Webhooks
Recibe eventos firmados, verifícalos y recupera lo que falte.
FITAL te avisa por HTTPS cuando algo cambia. Cada entrega es un POST firmado a tu destino: verifica la firma, responde 2xx rápido y procesa después. Para verlo funcionar en tu máquina, sigue el inicio rápido.
Requisitos del receptor
| Requisito | Valor |
|---|---|
| Protocolo | HTTPS en el puerto 443, sin usuario ni contraseña en la URL |
| Certificado | Vigente, emitido por una autoridad reconocida y válido para tu dominio. Uno autofirmado o vencido cuenta como fallo |
| Dirección | Tu dominio debe resolver a direcciones IPv4 públicas. Las entregas salen sólo por IPv4 |
| Conexión | Hasta 5 segundos para abrirla |
| Respuesta | 10 segundos para que FITAL reciba tu código de estado |
| Éxito | Cualquier 2xx. El cuerpo de tu respuesta se ignora y FITAL cierra la conexión al recibir el código: no dependas de seguir escribiendo después |
| Redirecciones | No se siguen: un 3xx cuenta como fallo |
La URL se valida al darla de alta y otra vez en cada entrega.
Cómo llega una entrega
Método POST, con Content-Type: application/json y estos encabezados:
| Encabezado | Contenido |
|---|---|
X-Fital-Timestamp | Segundos unix de este intento. Cambia en cada reintento |
X-Fital-Signature | Una o más firmas v1=<hex>, separadas por comas |
X-Fital-Event-Id | Identificador del evento. Sirve para depurar y no va firmado: para deduplicar usa el id del cuerpo |
X-Fital-Delivery-Id | Identificador de la entrega a este destino. Es el mismo en sus reintentos |
El cuerpo es el evento:
{
"id": "evt_5mPqW2xZr8TnC1vH7kLdS4bJ",
"type": "payment.completed",
"resource_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
"api_version": "2026-11-01",
"created_at": "2026-11-04T18:01:30.000000Z",
"sequence": 2,
"data": { "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB", "status": "completed" }
}data cambia según type. El catálogo de eventos trae el cuerpo de cada uno. Verifica la firma antes de interpretar el JSON: ver Verificar la firma.
Dar de alta un destino
Por API sólo se consulta, con GET /v1/webhook-endpoints. El alta, la edición, la baja, la rotación del secreto y la reactivación se hacen en el panel, y v1 no publica operaciones para eso: un destino decide a dónde sale tu información, y el panel exige sesión, rol y bitácora que una llave no da. En producción requiere rol de propietario o admin y login de hace menos de 5 minutos; en la cuenta de prueba, también rol de desarrollador.
- Crear. En la pantalla Webhooks elige Agregar webhook. Escribe la URL y los eventos que quieres recibir; sin elegir ninguno, recibes todos. Cada URL se registra una sola vez por ambiente.
- Guardar el secreto. El panel muestra el secreto de firma de ese destino una sola vez. Guárdalo en tu gestor de secretos.
- Verificar. Implementa Verificar la firma en tu receptor, con ese secreto.
- Probar. Abre Ver entregas y elige Enviar evento de prueba. Llega un evento
webhook.test, con{"test": true}endata, y el resultado aparece en esa misma lista. Ese evento no entra aGET /v1/events. - Activar. No hay un paso aparte: el destino nace
activey recibe eventos desde que lo creas. Créalo cuando tu receptor ya esté listo.
Estados de un destino
| Estado | Significa | Qué puedes hacer |
|---|---|---|
active | Recibe eventos | Probar, reenviar entregas, rotar el secreto y eliminarlo |
disabled | Dejó de recibir eventos tras 3 días seguidos de fallos | Reactivar, rotar el secreto y eliminarlo. El panel rechaza pruebas y reenvíos hasta reactivarlo |
paused | Valor reservado. Hoy ningún flujo lo asigna | Trátalo como un destino que no recibe eventos |
Reactivar un destino
Una entrega correcta reinicia la cuenta de fallos. Si los fallos siguen durante 3 días, el destino pasa a disabled y aparece un aviso crítico en la pantalla Avisos. Mientras esté así no se le envía nada, y lo pendiente queda skipped.
Corrige tu receptor y elige Reactivar en la pantalla Webhooks. El destino vuelve a active y su cuenta de fallos a cero. Lo que no recibió no se envía solo: recupéralo con GET /v1/events, o reenvía desde Ver entregas las que aparecen ahí.
Rotar el secreto
En Ver entregas elige Rotar secreto. Recibes el secreto nuevo una sola vez. Durante 24 horas llegan dos firmas v1=, con el secreto nuevo y con el anterior, separadas por comas. Basta que una valide, así que despliega el secreto nuevo en ese plazo.
Sólo existe un secreto anterior: rotar otra vez dentro de ese plazo retira de inmediato el que estaba en gracia.
Reintentos
Un intento falla con cualquier respuesta que no sea 2xx, con un plazo vencido, o con un error de conexión, de certificado o de DNS.
Hay un intento inicial más 10 reintentos, 11 en total, con esta espera entre ellos: 30 s, 1 min, 2 min, 5 min, 15 min, 30 min, 1 h, 2 h, 6 h, 12 h. La ventana total es de 21 h 53 min 30 s desde que termina el primer intento, más lo que tarde cada uno. Después la entrega queda exhausted.
Un evento tarda de 20 a 120 segundos en salir. Cada destino recibe hasta 5 entregas simultáneas.
El registro de entregas
Ver entregas muestra las últimas entregas de un destino, con sus intentos y su resultado. Desde ahí puedes reenviar una entrega ya terminada.
| Resultado | Estado de la entrega |
|---|---|
| Entregado | Tu receptor respondió 2xx |
| Falló | Sigue reintentando, o quedó exhausted |
| Descartado | Quedó skipped, sin entrega |
| Pendiente | Aún no se intenta, o está en curso |
La pantalla Registros de API muestra las peticiones que tu sistema hace a la API, no estas entregas.
El evento siempre queda guardado
La entrega HTTP tiene dos excepciones. Queda skipped si el destino no está activo o si la cola está llena: 2,000 entregas pendientes por destino y 10,000 por empresa. Queda exhausted al agotar los reintentos. Por eso consulta GET /v1/events periódicamente:
- Pide la lista sin
cursor: empieza en el evento más nuevo. - Sigue con
next_cursorhacia el pasado hasta llegar a un evento que ya procesaste, o hasta quenext_cursorseanull. - Procesa lo nuevo y guarda los
idvistos o la hora del último. - En la siguiente consulta, repasa desde 5 minutos antes del último evento visto: dos eventos casi simultáneos pueden confirmarse en otro orden.
No guardes next_cursor entre consultas. Sólo camina hacia el pasado y termina en null, así que nunca te mostraría un evento nuevo. El cursor que se guarda es el de GET /v1/movements.
Variables de estas muestras: simulador público, sin cuenta
Define esto en tu terminal. Con estos valores las muestras corren tal cual contra el simulador público:
export FITAL_API="https://widget.fital.xyz/explorar/v1"
export FITAL_KEY="sk_test_sandbox_publica"En la cuenta de prueba o en producción cambia la base, la llave y el secreto, y sustituye los id de ejemplo por los tuyos. Ver Ambientes.
# Empieza sin cursor, en el evento más nuevo. CURSOR sólo sirve para seguir esta vuelta hacia atrás: no lo guardes entre consultas.
CURSOR=""
curl -sS -G "$FITAL_API/events" \
-H "Authorization: Bearer $FITAL_KEY" \
--data-urlencode "limit=25" \
${CURSOR:+--data-urlencode "cursor=$CURSOR"}Duplicados y desorden
La entrega es al menos una vez y sin orden. Deduplica por el id del cuerpo, después de verificar la firma: la firma cubre el cuerpo y no los encabezados, así que X-Fital-Event-Id se puede cambiar sin invalidarla. Ordena por sequence: un pago completed puede pasar después a returned, y un retroceso real también se publica. Ver Orden.
Errores comunes
- Verificar sobre el JSON ya interpretado: cambia los bytes y la firma falla. Usa el cuerpo crudo.
- Deduplicar por
X-Fital-Delivery-Id: otro destino recibe el mismo evento con otro valor. Usa eliddel cuerpo. - Deduplicar por
X-Fital-Event-Id: no va firmado. Quien capture una entrega podría repetirla con otro valor dentro de la ventana de 5 minutos y procesarla dos veces. Usa eliddel cuerpo. - Responder
2xxhasta terminar de procesar: todo tu trabajo tendría que caber en 10 segundos. Guarda el evento, responde y procesa después. - Guardar
next_cursordeGET /v1/eventspara la siguiente consulta: no verías eventos nuevos. - Confiar en la IP de origen. La firma es la protección principal. Ver IP de webhooks.