Guía de webhooks
Los webhooks transportan todas las decisiones asincrónicas de esta integración. Hay dos flujos, y esta página cubre los dos.
| Flujo | Receptor | Operación |
|---|---|---|
| De Nexa a Tesser, decisiones de acreditación y fallas del desembolso | Tesser [T] | POST /v1/webhooks/nexa/{organizationId} |
| De Tesser a Nexa, estado del retiro para tesorería | Nexa [N] | POST /v1/webhooks/tesser |
Nexa → Tesser
El envoltorio
Todo evento de Nexa hacia Tesser usa el mismo sobre:
Code
event_ides un UUID generado por Nexa. Es la clave de deduplicación.occurred_atestá en formato RFC 3339 UTC.datavaría según elevent_type, como se detalla abajo.
Catálogo de eventos
event_type | Significado | data |
|---|---|---|
quote.credit.sent | Nexa decidió acreditar; la instrucción ya se envió al core bancario | tesser_quote_id, quote_status: "CREDIT_SENT", gtq_amount |
quote.credit.confirmed | Los GTQ ya están disponibles en la cuenta de Felix en Nexa | tesser_quote_id, quote_status: "CREDIT_CONFIRMED", gtq_amount (valor autoritativo, que puede diferir de quoted_gtq según lo decida Nexa), credited_at |
quote.additional_funds_requested | Nexa quiere más USDC antes de acreditar | tesser_quote_id, quote_status: "DEPOSIT_REPORTED", usdc_total_reported, usdc_additional_requested, note (texto libre) |
quote.rejected | Nexa se niega a acreditar | tesser_quote_id, quote_status: "REJECTED", reason_code (RISK, AMOUNT u OTHER), note |
payment.status.changed | El desembolso falló o fue devuelto después de que Nexa ya había respondido COMPLETED. Se envía solo ante una falla; no existe un evento de éxito | tesser_quote_id, nexa_payment_id, payment_status (FAILED o RETURNED), quote_status, status_metadata (objeto de forma libre: referencias de la red de pagos, motivos de la falla) |
El desembolso en sí es sincrónico: POST /v1/payments devuelve el
resultado, así que un desembolso exitoso no produce ningún evento. Véase
4 · Desembolso en GTQ.
status_metadata aparece únicamente en payment.status.changed, que es
donde las referencias de la red de pagos y los motivos de la falla sí varían
de verdad.
Contrato de entrega
Al menos una vez. Tesser deduplica por event_id. Nexa reintenta ante
cualquier respuesta que no sea 2xx, con backoff exponencial, propuesto hasta
por 24 horas. La política real del emisor de Nexa es la
pregunta abierta 6.
Éxito = cualquier 2xx con cuerpo vacío. Una respuesta que no sea 2xx, o un timeout, significa reintento.
El orden no está garantizado. Aplica la regla de alcanzabilidad, es decir,
solo avance de estado. La máquina de estados
define qué estados son alcanzables desde cuáles. Para el aspecto de avance
de estado de cada evento, el receptor compara el quote_status del evento
contra el estado actual:
Relación del quote_status del evento con el estado actual | Comportamiento del receptor |
|---|---|
| Alcanzable hacia adelante desde el estado actual | Se aplica. Es un avance genuino. |
| El estado actual mismo, o un estado anterior | Se responde 200 y no se cambia nada. Es un duplicado viejo o un reordenamiento. |
Una rama distinta o un estado terminal en conflicto, por ejemplo quote.credit.sent después de quote.rejected, o PAYMENT_FAILED después de REJECTED | Se responde 200 y se levanta una alerta de operaciones. Esto es un incidente de integridad de datos entre las dos empresas, no un reordenamiento. Ignorarlo en silencio ocultaría una divergencia real de dinero. |
El evento de falla tardía no es un conflicto. PAYMENT_FAILED y
PAYMENT_RETURNED son alcanzables hacia adelante desde
PAYMENT_COMPLETED, así que una falla que llega después de una respuesta de
desembolso exitosa se maneja con la cláusula normal de alcanzabilidad hacia
adelante: se aplica, sin alerta. Es la vía esperada de la falla tardía, y es
el único evento de desembolso que existe.
Los eventos que no avanzan el estado también se procesan. Algunos eventos
traen una acción o información nueva sin mover la máquina de estados:
quote.additional_funds_requested (la cotización se queda en
DEPOSIT_REPORTED, pero Tesser debe actuar) y cualquier
payment.status.changed que repita una falla ya registrada. Estos se
procesan en la primera entrega de cada event_id; las entregas
posteriores del mismo event_id se deduplican como siempre. La regla de
alcanzabilidad rige únicamente el campo quote_status, nunca si el contenido
del evento se procesa o no.
Autenticación
Nexa presenta su certificado de cliente; Tesser resuelve la identidad de Nexa
a partir de la huella digital registrada, y la identidad resuelta debe
coincidir con el {organizationId} de la ruta. Una discrepancia da 401.
El detalle completo está en Autenticación.
La conectividad y las credenciales se pueden comprobar sin enviar ningún
evento:
HEAD /v1/webhooks/nexa/{organizationId}
[T].
Tesser → Nexa
Nexa aloja
POST /v1/webhooks/tesser
[N] para recibir los webhooks salientes estándar de Tesser. En este POC
la suscripción que Nexa necesita es la de actualizaciones del estado del
retiro, que da soporte a
POST /v1/treasury/withdrawals
[T] en la fase 5.
El sobre y los tipos de evento son los que Tesser ya tiene. Este portal documenta cuál suscripción necesita Nexa y enlaza a docs.tesser.xyz para las cadenas de los tipos de evento, en lugar de volver a especificar un contrato que ya existe y ya está en producción en otro lado.
Autenticación de la entrega: hoy, la firma Ed25519 del encabezado
X-Tesser-Signature, más un certificado de cliente de Tesser en cuanto esté
listo el trabajo de mTLS saliente del emisor. Ver
Autenticación.
Lista de verificación para quien implementa (ambas direcciones)
- Deduplicar por el id del evento antes de hacer cualquier otra cosa. "Al menos una vez" significa que los duplicados son tráfico normal, no un incidente.
- Responder rápido y procesar después. Cualquier 2xx con cuerpo vacío da por terminado el intento de entrega. No conviene mantener la conexión abierta mientras se hace el trabajo posterior.
- Devolver 5xx solo cuando un reintento pueda servir de algo. Un payload
mal formado es
400y nunca va a tener éxito; reintentarlo desperdicia el tiempo de ambas partes. - Nunca deducir el estado a partir del orden de llegada. Hay que
comparar
quote_statuscontra el estado actual y aplicar la regla de alcanzabilidad. - Alertar sobre los eventos en conflicto en lugar de descartarlos. Un estado terminal en conflicto significa que las dos empresas no coinciden respecto al dinero.
- Mantener una vía de recuperación. Si se pierde un evento,
GET /v1/quotes/{tesser_quote_id}[N] yGET /v1/payments/{nexa_payment_id}[N] responden "¿en qué estado creen ustedes que está?" en una sola llamada.