Proteside Docs

Recetas

Flujos completos con curl para registrar tiendas, revisar scripts, crear reglas, gestionar alertas, descargar reportes PCI DSS y configurar webhooks.

Cada receta es un flujo de principio a fin que puedes copiar y adaptar. Los ejemplos usan curl y jq y estas variables:

export B="https://app.proteside.com/api/v1"
export PROTESIDE_TOKEN="ps_live_Ab3dEf6hIj9kLm2nOp5qRs8t_..."

Los IDs y datos de las respuestas son ficticios. Las respuestas aparecen resumidas: "..." indica campos omitidos.

Crear una tienda y obtener el snippet del SDK

Alcances: stores:write y stores:read.

Crea la tienda

Indica el dominio, los medios de pago y, si quieres, las páginas de pago y las claves de cobro. El esquema (https://), la ruta y el puerto del dominio se descartan.

curl -s -X POST "$B/stores" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "domain": "https://www.mitienda.com/",
    "name": "Mi Tienda",
    "payment_methods": ["pix", "card"],
    "expected_iframe_origins": ["js.stripe.com"],
    "recipients": [{ "method": "pix", "key": "12.345.678/0001-90", "label": "CNPJ principal" }],
    "payment_pages": [{ "url_pattern": "/checkout/*", "payment_methods": ["pix", "card"] }]
  }' | tee tienda.json | jq '{id, domain, sdk_key, safe_slug}'
{
  "id": "0c1d2e3f-1111-4222-8333-444455556666",
  "domain": "mitienda.com",
  "sdk_key": "pk_live_3f9c0a1b2c3d4e5f60718293a4b5c6d7",
  "safe_slug": "mitienda-ps3fa9c1"
}

La respuesta completa (201) también incluye sdk_config, recipients (la clave Pix vuelve enmascarada, como **.345.678/****-**; la clave en claro no se guarda), pages y snippets con el código de instalación para HTML, Next.js, Nuxt y WordPress. La tienda también recibe un canal "E-mail (padrão)" para el dueño de la organización.

Guarda el snippet

jq -r '.snippets.html' tienda.json > proteside-snippet.html

El snippet tiene dos partes, entre <!-- Proteside Start --> y <!-- Proteside End -->: un script inline (el bootstrapper) y el shield.js cargado desde la CDN con data-key igual a la sdk_key. Pega el bloque completo como primer elemento del <head> de la página de checkout, siguiendo la guía de instalación.

Revisa la instalación

Después de publicar el snippet y abrir el checkout, consulta el estado:

STORE=$(jq -r .id tienda.json)
curl -s "$B/stores/$STORE/status" -H "Authorization: Bearer $PROTESIDE_TOKEN"
{
  "id": "0c1d2e3f-1111-4222-8333-444455556666",
  "domain": "mitienda.com",
  "status": "active",
  "installed": true,
  "last_event_at": "2026-10-06T12:20:12.000Z",
  "entitled": true,
  "paused": false,
  "channel": "stable",
  "mode": "monitor",
  "developer_mode": false
}

installed queda en true cuando el SDK envió algún evento en las últimas 24 horas.

ErrorCausa
409 conflictEl dominio ya está protegido por otra tienda, de cualquier organización
403 plan_limitEl plan alcanzó el límite de tiendas (la respuesta incluye limit y plan)
400 invalid_requestDominio inválido o campo desconocido

Para cambiar la clave del SDK más adelante, usa POST /stores/{id}/rotate-key: la respuesta trae la nueva sdk_key y los snippets, y la clave anterior sigue siendo válida durante 24 horas (previous_key_valid_until).

Revisar scripts pendientes y autorizarlos con justificación

Alcances: scripts:read y scripts:review. PCI DSS 4.0, requisito 6.4.3, pide que cada script de la página de pago se autorice con una justificación. La API registra la justificación, la fecha y el autor (api:<prefijo>).

Lista lo que espera revisión

curl -s "$B/scripts?store_id=$STORE&status=needs_review&limit=100" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  | jq '.data[] | {id, src, type, category, first_seen_at}'
{
  "id": "b2c3d4e5-6666-4777-8888-999900001111",
  "src": "https://www.googletagmanager.com/gtm.js",
  "type": "known_third",
  "category": "tag_manager",
  "first_seen_at": "2026-10-05T18:02:44.000Z"
}

Autoriza con justificación

La justification es obligatoria (10 a 2000 caracteres). expires_days (1 a 3650) define cuándo vence la autorización y el script vuelve a revisión.

SCRIPT=b2c3d4e5-6666-4777-8888-999900001111

curl -s -X POST "$B/scripts/$SCRIPT/authorize" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "justification": "Contenedor GTM de marketing; no accede a campos de pago. Aprobado en el ticket SEC-142.",
    "expires_days": 90
  }'
{
  "id": "b2c3d4e5-6666-4777-8888-999900001111",
  "src": "https://www.googletagmanager.com/gtm.js",
  "status": "authorized",
  "authorization_method": "api",
  "authorized_by": "api:ps_live_Ab3dEf6hIj9kLm2nOp5qRs8t",
  "justification": "Contenedor GTM de marketing; no accede a campos de pago. Aprobado en el ticket SEC-142.",
  "reviewed_at": "2026-10-06T12:00:00.000Z",
  "expires_at": "2027-01-04T12:00:00.000Z",
  "review_due_at": "2027-01-04T12:00:00.000Z",
  "...": "...",
  "block_rules_deactivated": false
}

Al autorizar, se desactivan las reglas de bloqueo activas que coinciden con el script (por dominio, URL o hash).

Diferencias respecto al spec

Los scripts autorizados mediante la API quedan con authorization_method: "api" (el spec dice manual y no incluye api entre los valores posibles). El campo block_rules_deactivated es un booleano que indica si se desactivó alguna regla, y no un conteo. Si generaste un cliente a partir del spec, acepta estos valores.

Para bloquear en lugar de autorizar, usa POST /scripts/{id}/block con una justification opcional. Proteside crea la regla de bloqueo por sí solo: por hash, para un script inline, o por dominio, para un script externo. La regla viene en rule en la respuesta.

Crear una regla de bloqueo por dominio

Alcance: rules:write (y scripts:read para listar reglas).

curl -s -X POST "$B/rules" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "store_id": "'"$STORE"'",
    "type": "block",
    "target": "domain",
    "value": "cdn-skimmer.example",
    "label": "Skimmer conocido"
  }'
{
  "id": "4d5e6f70-aaaa-4bbb-8ccc-dddd00001111",
  "store_id": "0c1d2e3f-1111-4222-8333-444455556666",
  "type": "block",
  "target": "domain",
  "value": "cdn-skimmer.example",
  "label": "Skimmer conocido",
  "active": true,
  "created_at": "2026-10-06T12:01:00.000Z"
}
  • target acepta domain, src (URL del script) o hash; type acepta block o allow.
  • Si ya existe una regla igual (misma tienda, tipo, objetivo y valor), se reactiva en lugar de duplicarse.
  • Las reglas activas llegan al SDK en la configuración de la tienda.

Para desactivarla sin eliminarla:

curl -s -X PATCH "$B/rules/4d5e6f70-aaaa-4bbb-8ccc-dddd00001111" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"active": false}'

Para eliminarla: DELETE $B/rules/{id}, que responde {"id": "...", "deleted": true}.

Listar alertas abiertas y resolverlas

Alcances: alerts:read y alerts:write.

curl -s "$B/alerts?store_id=$STORE&status=open&severity=critical&since=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  | jq '.data[] | {id, type, severity, page, occurrences, last_seen_at}'
{
  "id": "e1f2a3b4-8888-4999-8000-111122223333",
  "type": "PIX_TAMPERED",
  "severity": "critical",
  "page": "/checkout/pagamento",
  "occurrences": 3,
  "last_seen_at": "2026-10-06T09:52:10.000Z"
}

since filtra por la última ocurrencia de la alerta. Después de investigar, resuélvela con una nota:

curl -s -X PATCH "$B/alerts/e1f2a3b4-8888-4999-8000-111122223333" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "resolved", "note": "Falso positivo: clave Pix nueva registrada por finanzas."}'
{
  "id": "e1f2a3b4-8888-4999-8000-111122223333",
  "type": "PIX_TAMPERED",
  "severity": "critical",
  "status": "resolved",
  "resolved_at": "2026-10-06T12:02:00.000Z",
  "resolved_by_label": "api:ps_live_Ab3dEf6hIj9kLm2nOp5qRs8t",
  "note": "Falso positivo: clave Pix nueva registrada por finanzas.",
  "...": "..."
}

Para reabrirla, envía {"status": "open"}. Si ya hay otra alerta abierta del mismo tipo y origen, la respuesta es 409 conflict.

Resolver una alerta no dispara el webhook alert.resolved. Si otros sistemas necesitan saberlo, avísales desde tu propia integración.

Descargar el reporte PCI DSS en PDF

Alcance: reports:read.

Sin fechas, cada llamada genera un reporte nuevo

GET /reports/pci sin period_start y period_end usa "los últimos 7 días hasta ahora". Como "ahora" cambia en cada llamada, Proteside genera un reporte nuevo cada vez, lo que es lento y llena el historial. Para reportes recurrentes, lista los que ya existen; para un período específico, envía fechas fijas.

Opción 1: descargar un reporte que ya existe. Lista los reportes de la tienda y descarga el PDF por su id:

curl -s "$B/reports/pci/snapshots?store_id=$STORE&kind=weekly&limit=1" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN"
{
  "data": [
    {
      "id": "5a6b7c8d-9999-4aaa-8bbb-cccc0000dddd",
      "store_id": "0c1d2e3f-1111-4222-8333-444455556666",
      "kind": "weekly",
      "period_start": "2026-09-28T09:00:00.000Z",
      "period_end": "2026-10-05T09:00:00.000Z",
      "locale": "pt-BR",
      "schema_version": 2,
      "sha256": "9b74c9897bac770ffc029102a200c5de3e2b4c1f0a8d7e6c5b4a392817060504",
      "generated_by": "cron:weekly-reports",
      "has_pdf": true,
      "csv_tables": ["scripts", "headers", "alerts"],
      "created_at": "2026-10-05T09:02:11.000Z"
    }
  ],
  "next_cursor": "WyIyMDI2LTEwLTA1VDA5OjAyOjExLjAwMFoiLCI1YTZiN2M4ZC05OTk5LTRhYWEtOGJiYi1jY2NjMDAwMGRkZGQiXQ"
}
curl -sL "$B/reports/pci/snapshots/5a6b7c8d-9999-4aaa-8bbb-cccc0000dddd?format=pdf" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -o pci-2026-09-28.pdf

Opción 2: generar el reporte de un período cerrado. Envía fechas fijas, de preferencia días completos. Repetir la misma llamada reutiliza el reporte ya generado:

curl -sL "$B/reports/pci?store_id=$STORE&period_start=2026-09-01T00:00:00Z&period_end=2026-10-01T00:00:00Z&format=pdf&locale=pt-BR" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -o pci-2026-09.pdf
  • Para PDF, la API responde 302 con un enlace firmado válido durante 1 hora; el -L de curl sigue el enlace. En otros lenguajes, sigue la redirección sin reenviar el header Authorization.
  • El período máximo es de 366 días, y period_end no puede estar en el futuro.
  • format=json devuelve los datos del reporte; format=csv&csv_table=alerts (o scripts, headers) devuelve una tabla en CSV.
  • locale acepta pt-BR o en.
  • Para recibir el reporte automáticamente cada semana o cada mes, prográmalo con POST /reports/pci/schedule (alcance reports:generate) y suscríbete al evento report.ready en un webhook.

Crear un webhook y disparar una prueba

Alcance: webhooks:manage.

Crea el webhook

curl -s -X POST "$B/webhooks" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://ops.mitienda.com/hooks/proteside",
    "events": ["alert.created", "integrity.changed", "sdk.silent", "report.ready"],
    "min_severity": "medium"
  }' | tee webhook.json | jq '{id, has_secret, secret}'
{
  "id": "a9b8c7d6-5555-4666-8777-888899990000",
  "has_secret": true,
  "secret": "whsec_9f8e7d6c5b4a39281706f5e4d3c2b1a0"
}

Guarda el secret en la bóveda de secretos del servicio que recibe el webhook: no vuelve a aparecer. Usamos min_severity: "medium" para recibir también sdk.silent.

Dispara la prueba

WH=$(jq -r .id webhook.json)
curl -s -X POST "$B/webhooks/$WH/test" -H "Authorization: Bearer $PROTESIDE_TOKEN"
{ "id": "a9b8c7d6-5555-4666-8777-888899990000", "delivered": true, "error": null }

La respuesta siempre es 200. Si delivered viene en false, el motivo está en error (por ejemplo http_401: ... cuando tu endpoint rechazó la firma, o timeout).

Valida la firma en el receptor

Implementa la verificación de X-Proteside-Signature como en Verificar la firma y repite la prueba hasta que delivered venga en true.

Partner: crear una tienda en una organización hija

Alcances: orgs:manage, stores:write, stores:read y webhooks:manage. Consulta Partners para ver el modelo completo.

Crea la organización hija

curl -s -X POST "$B/organizations" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name": "Mi Tienda", "owner_email": "tu@mitienda.com", "plan": "growth"}' \
  | tee hija.json | jq '{id, billing_mode, owner_invite}'
{ "id": "11112222-3333-4444-8555-666677778888", "billing_mode": "partner", "owner_invite": "sent" }

Crea la tienda en la hija

Pasa org_id en la query string. Usa una Idempotency-Key nueva: la clave vale para la organización del token, no para la hija.

CHILD=$(jq -r .id hija.json)

curl -s -X POST "$B/stores?org_id=$CHILD" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"domain": "mitienda.com", "payment_methods": ["pix", "card"]}' \
  | tee tienda-hija.json | jq '{id, org_id, sdk_key}'
{
  "id": "0c1d2e3f-1111-4222-8333-444455556666",
  "org_id": "11112222-3333-4444-8555-666677778888",
  "sdk_key": "pk_live_3f9c0a1b2c3d4e5f60718293a4b5c6d7"
}

Entrega al cliente el snippet de .snippets.html (o instálalo tú mismo).

Crea el webhook de la hija

Los eventos de la hija solo van a los webhooks creados en ella:

curl -s -X POST "$B/webhooks?org_id=$CHILD" \
  -H "Authorization: Bearer $PROTESIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://ops.parceiro.example/hooks/proteside", "min_severity": "medium"}' \
  | jq '{id, secret}'

Sigue la instalación y el consumo

STORE=$(jq -r .id tienda-hija.json)
curl -s "$B/stores/$STORE/status?org_id=$CHILD" -H "Authorization: Bearer $PROTESIDE_TOKEN"
curl -s "$B/organizations/$CHILD/usage" -H "Authorization: Bearer $PROTESIDE_TOKEN"

En esta página