Webhooks: cómo te avisa SPIDI
Un pago ocurre en dos fases, y las dos te importan. Un webhook es cómo te enteras de cada una sin tener que preguntar: un aviso que SPIDI te envía por POST en cuanto algo pasa, sin depender de que la persona vuelva a tu sitio.
Esta página explica qué son los avisos y cuáles existen. Para implementarlos —verificar la firma, deduplicar, responder— ve a → Manejar notificaciones.
Qué trae un aviso
SPIDI hace POST a tu webhook_url con un cuerpo JSON y tres cabeceras:
spidi-signature— sello HMAC-SHA256 despidi-timestamp+"."+ el cuerpo crudo, con el secreto de tu cuenta.spidi-timestamp— cuándo se generó. Entra en el cálculo de la firma, así que sin él no se puede verificar nada.idempotency-key— identificador estable del envío, para deduplicar reintentos.
{ "event": "payment_session.paid", "data": { /* ... */ } }
El campo event es lo que tu código mira. Todo lo demás de esta página gira alrededor de él.
Los cinco eventos que declara el contrato
event que recibes | Cuándo | Fase |
|---|---|---|
payment_session.created | La sesión de pago se creó | antes de la fase 1 |
payment_session.paid | El débito al pagador salió bien | fase 1 |
payment_session.accredited | Terminaron las acreditaciones | fase 2 |
payment_session.accreditation_to_recipient_completed | Se acreditó a un receptor del reparto | fase 2, uno por receptor |
payment_session.accreditation_to_recipient_failed | Falló la acreditación a un receptor | fase 2 |
En un pago sin reparto verás dos: paid y accredited.
La trampa: dos de ellos no se llaman como el contrato los titula
event, no contra el título del contratoLa clave que titula cada webhook en la OpenAPI es una etiqueta del documento: no viaja por el cable. Y en dos casos no coincide con lo que llega:
| Clave en la OpenAPI | event que viaja de verdad |
|---|---|
payment_session.payment_completed | payment_session.paid |
payment_session.accreditations_completed | payment_session.accredited |
El fallo es silencioso. Un switch escrito leyendo los títulos del contrato no entra nunca en esas dos ramas: los avisos llegan, tu endpoint responde 2xx, y tu lógica no se ejecuta. Nada da error.
Cinco declarados, dos observados
Los cinco están en el contrato. Nosotros solo hemos visto llegar paid y accredited, y el simulador solo emite esos dos. Los otros tres están aquí porque el contrato los declara y tu receptor puede recibirlos: ante un event que no conozcas, ignóralo y responde 2xx — nunca rompas.
El que más conviene mirar es accreditation_to_recipient_failed. Es el único que dice que el dinero no llegó a un receptor, y no tiene equivalente en GET status: si no lo escuchas, un reparto fallido se te queda invisible.
Cuántos avisos llegan por un pago
Llega uno por cada transacción de crédito, y eso depende de si repartes:
- Sin reparto — una sola parte, un solo
payment_session.accredited. - Con reparto — una acreditación por receptor, más el cierre. Todas a tu mismo
webhook_url: un partner no tiene webhook propio.
→ Split
Entrega: reintentos, deduplicación, y qué pasa si estabas caído
Si tu endpoint no responde 2xx a tiempo, SPIDI reintenta con el mismo idempotency-key. Por eso tu receptor debe deduplicar: procesar una vez aunque llegue varias.
No escribas lógica que dependa de cuántos reintentos hay ni de cuánto esperan entre uno y otro. Diseña para recibir el mismo aviso un número indeterminado de veces, que es la única suposición que no se rompe.
session_id: si SPIDI se rinde, es tu única llave de vueltaDespués de reintentar, SPIDI se rinde, y no hay reenvío. Cuando tu servidor vuelva no habrá un aviso esperándote — habrá una sesión que puedes consultar. Pero no hay forma de listar tus sesiones, así que sin el session_id guardado no tienes por dónde entrar.
El aviso es el atajo, no la única fuente. Las dos fases se consultan también en la sesión, con GET status: la fase 1 en data.status y la fase 2 en data.session_payment.receiver_credits. → Transacción a dos fases