Cómo manejar webhooks PSE
Recibe las entregas de webhook PSE y enruta cada estado del intent al handler correcto.
Prerequisitos
Antes de comenzar:
- Tu endpoint de webhook es HTTPS y públicamente accesible.
- Leíste Conceptos Clave y entiendes el ciclo de vida del intent PSE.
- Puedes configurar Effects en tu wallet.
Siempre responde 200 OK a las entregas de webhook. Kamin reintenta en
cualquier respuesta que no sea 200 con backoff exponencial hasta 20
intentos — por eso tu handler debe ser idempotente sobre
(intentHandle, status).
Configuración de Effects
Un pago PSE emite tres señales que tu backend debe atender: la URL de autorización del banco (después del registro), la confirmación de que la transacción PSE terminó exitosamente, y el estado final del intent. Configura dos Effects para capturarlas — uno para los pasos de la transacción, otro para el estado final.
Ambos effects se registran en Kamin Ledger mediante de la misma manera que en la guía de Cómo registrar y escuchar un efecto de estado de intent. Esa guía cubre la autenticación, el hashing y la firma — las subsecciones siguientes describen las partes específicas de PSE: el signal, el filter y el payload del webhook que entrega cada effect.
Incluye tu dominio en el handle del effect (p. ej., pse-intent-progress@<dominio>). Sin el sufijo @<dominio>, el effect no puede registrarse contra tu wallet.
Effect 1: pasos de la transacción PSE (intent-proofs-added)
Se dispara cada vez que se agrega un nuevo proof a un intent PSE. El payload lleva el handle del intent más únicamente el proof recién agregado — no el historial completo del intent. Usa este effect para controlar el flujo de cara al cliente:
registered— llevapseUrl, la URL de autorización del banco. Envíala al navegador del cliente y redirígelo a su banco para autorizar el pago.prepared— llevacoreId(el mismo CUS, renombrado para uso interno). PSE procesó el pago exitosamente. Muestra al cliente una pantalla de éxito.
Crear el Effect
Hashea, firma y envía al endpoint Create Effect. Consulta Autenticación para detalles de hashing y firma.
POST https://<ledger-url>/api/v2/effectsAccept: application/json, text/plain, */*
Content-Type: application/json
x-ledger: <ledger-handle>{
"data": {
"handle": "pse-intent-progress@<dominio>",
"signal": "intent-proofs-added",
"filter": {
"proofs.0.signer": "pse-bridge",
"proofs.0.custom.status": {
"$in": ["registered", "prepared"]
}
},
"action": {
"schema": "webhook",
"endpoint": "<endpoint-webhook>"
},
"access": [
{
"action": "any",
"signer": { "public": "<clave-publica>" }
}
]
},
"hash": "<sha256-hash-of-data>",
"meta": {
"proofs": [
{
"custom": { "moment": "<iso-8601-timestamp>" },
"method": "ed25519-raw",
"public": "<clave-publica>",
"result": "<firma-del-hash>"
}
]
}
}Respuesta:
{
"hash": "<sha256-hash-of-data>",
"data": {
"handle": "pse-intent-progress@<dominio>",
"signal": "intent-proofs-added",
"filter": {
"proofs.0.signer": "pse-bridge",
"proofs.0.custom.status": { "$in": ["registered", "prepared"] }
},
"action": {
"schema": "webhook",
"endpoint": "<endpoint-webhook>"
},
"access": [
{ "action": "any", "signer": { "public": "<clave-publica>" } }
]
},
"luid": "<luid-del-effect>",
"meta": {
"proofs": [
{
"custom": {
"moment": "<iso-8601-timestamp>",
"status": "created"
},
"method": "ed25519-raw",
"public": "<clave-publica>",
"result": "<firma-del-hash>"
},
{
"custom": {
"luid": "<luid-del-effect>",
"moment": "<iso-8601-timestamp>",
"status": "created"
},
"method": "ed25519-v2",
"public": "<clave-publica-del-ledger>",
"result": "<firma-del-ledger>"
}
],
"status": "created",
"moment": "<iso-8601-timestamp>",
"owners": ["<clave-publica>"]
}
}Por qué estos filtros:
proofs.0.signer: "pse-bridge"— El effect se dispara con cada proof nuevo. Un mismostatuspuede venir firmado por múltiples actores — tu propia firma al confirmar el intent, proofs internos del ledger, etc. La firma del PSE bridge marca el evento como originado en el rail PSE; este filtro descarta los proofs firmados por otros actores para que manejes cada evento exactamente una vez.proofs.0.custom.status: { "$in": ["registered", "prepared"] }— Restringe la entrega a los dos hitos de la transacción PSE de los que este effect es responsable.
Ejemplos de llamados
Una vez creado el effect, cada proof firmado por el PSE bridge con uno de los estados configurados dispara una entrega a tu endpoint webhook. data.intent es el string del handle del intent y data.proofs contiene únicamente el proof recién agregado.
En el proof registered, los campos pseUrl y trazabilityCode son los
datos accionables clave: redirige al cliente a pseUrl y persiste el
trazabilityCode (CUS) vinculado al handle del intent para usarlo en las
pantallas de recibo.
Effect 2: notificación de estado final (intent-updated)
Se dispara cuando un intent PSE transiciona a un estado final. El payload lleva el objeto intent completo con su historial completo de proofs. Usa este effect para manejar los resultados finales:
completed— Kamin acreditópcopa tu wallet de cliente. El pago está completamente liquidado.rejected— El intent falló. La lista completa de proofs te permite encontrar el prooffailedy extraer el detalle del rechazo.
Crear el Effect
Hashea, firma y envía al endpoint Create Effect.
POST https://<ledger-url>/api/v2/effectsAccept: application/json, text/plain, */*
Content-Type: application/json
x-ledger: <ledger-handle>{
"data": {
"handle": "pse-intent-completion@<dominio>",
"signal": "intent-updated",
"filter": {
"intent.data.schema": "pse",
"intent.meta.status": {
"$in": ["completed", "rejected"]
}
},
"action": {
"schema": "webhook",
"endpoint": "<endpoint-webhook>"
},
"access": [
{
"action": "any",
"signer": { "public": "<clave-publica>" }
}
]
},
"hash": "<sha256-hash-of-data>",
"meta": {
"proofs": [
{
"custom": { "moment": "<iso-8601-timestamp>" },
"method": "ed25519-raw",
"public": "<clave-publica>",
"result": "<firma-del-hash>"
}
]
}
}Respuesta:
{
"hash": "<sha256-hash-of-data>",
"data": {
"handle": "pse-intent-completion@<dominio>",
"signal": "intent-updated",
"filter": {
"intent.data.schema": "pse",
"intent.meta.status": { "$in": ["completed", "rejected"] }
},
"action": {
"schema": "webhook",
"endpoint": "<endpoint-webhook>"
},
"access": [
{ "action": "any", "signer": { "public": "<clave-publica>" } }
]
},
"luid": "<luid-del-effect>",
"meta": {
"proofs": [
{
"custom": {
"moment": "<iso-8601-timestamp>",
"status": "created"
},
"method": "ed25519-raw",
"public": "<clave-publica>",
"result": "<firma-del-hash>"
},
{
"custom": {
"luid": "<luid-del-effect>",
"moment": "<iso-8601-timestamp>",
"status": "created"
},
"method": "ed25519-v2",
"public": "<clave-publica-del-ledger>",
"result": "<firma-del-ledger>"
}
],
"status": "created",
"moment": "<iso-8601-timestamp>",
"owners": ["<clave-publica>"]
}
}Por qué estos filtros:
intent.data.schema: "pse"— Limita la entrega únicamente a intents PSE. Tu wallet puede procesar otros schemas para otros rails.intent.meta.status: { "$in": ["completed", "rejected"] }— Restringe a estados finales. Las transiciones anteriores ya están cubiertas por el effectintent-proofs-added.
Ejemplos de llamados
Una vez creado el effect, cada transición final de un intent PSE dispara una entrega a tu endpoint webhook. data.intent es el objeto intent completo con su historial completo de proofs; los proofs viven bajo data.intent.meta.proofs.
Manejar cada estado
registered — Redirigir al cliente
El proof registered lleva pseUrl (la URL de autorización del banco) y trazabilityCode (el CUS PSE). Envíalos al navegador en espera y redirige al cliente a pseUrl. Persiste trazabilityCode vinculado al handle del intent — lo necesitarás para las pantallas de recibo después de la redirección bancaria.
{
"custom": {
"status": "registered",
"moment": "<iso-8601-timestamp>",
"pseUrl": "<url-de-autorizacion-del-banco>",
"trazabilityCode": "<cus>"
}
}prepared — Mostrar éxito
El proof prepared confirma que la transacción está procesada en PSE. Lleva el trazabilityCode y su alias coreId (mismo valor CUS). Marca la orden como exitosa y muestra al cliente una pantalla de éxito.
{
"custom": {
"status": "prepared",
"moment": "<iso-8601-timestamp>",
"coreId": "<cus>",
"trazabilityCode": "<cus>"
}
}completed — Liquidación confirmada
El proof completed significa que Kamin Ledger acreditó pcop a tu wallet del cliente. El pago está completamente liquidado. En la mayoría de integraciones, la pantalla de éxito del cliente ya se está mostrando desde prepared — completed es relevante para la reconciliación de back-office.
El tiempo entre prepared y completed depende de los ciclos de
liquidación de PSE. El cambio de estado se dispara con el crédito que ocurre
al cierre del ciclo en el que Kamin recibe los fondos — no inmediatamente
después de prepared. Planea la reconciliación de back-office considerando
este retraso en lugar de esperar los dos webhooks consecutivos.
rejected — Mostrar fallo
rejected es el estado final del intent — el marcador de que el cobro falló. Confirmalo con intent.meta.status === "rejected" o buscando el proof rejected firmado por el PSE bridge en data.intent.meta.proofs.
Para obtener el motivo del fallo, revisa la lista de proofs buscando el proof anterior donde signer === "pse-bridge" y custom.status === "failed" — ese proof lleva los campos detail y reason. El proof rejected por sí mismo solo marca la transición final de estado.
{
"custom": {
"status": "failed",
"moment": "<iso-8601-timestamp>",
"detail": "<codigo-error-pse>",
"reason": "bridge.entry-rejected"
}
}Siguiente paso
Con esto ya tienes los Effects configurados y tus handlers listos para recibir los webhooks de Kamin ante cada cambio de estado de un intent PSE. En la siguiente sesión continuaremos con Cómo cobrar un pago PSE, donde construirás el intent PSE firmado, redirigirás al cliente a su banco y cerrarás el ciclo con los webhooks que acabas de configurar.
Historial de cambios
- Agregado• Version inicial