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.htmlEl 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.
| Error | Causa |
|---|---|
409 conflict | El dominio ya está protegido por otra tienda, de cualquier organización |
403 plan_limit | El plan alcanzó el límite de tiendas (la respuesta incluye limit y plan) |
400 invalid_request | Dominio 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"
}targetaceptadomain,src(URL del script) ohash;typeaceptablockoallow.- 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.pdfOpció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
302con un enlace firmado válido durante 1 hora; el-Ldecurlsigue el enlace. En otros lenguajes, sigue la redirección sin reenviar el headerAuthorization. - El período máximo es de 366 días, y
period_endno puede estar en el futuro. format=jsondevuelve los datos del reporte;format=csv&csv_table=alerts(oscripts,headers) devuelve una tabla en CSV.localeaceptapt-BRoen.- Para recibir el reporte automáticamente cada semana o cada mes, prográmalo con
POST /reports/pci/schedule(alcancereports:generate) y suscríbete al eventoreport.readyen 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"