FITALAPI

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

RequisitoValor
ProtocoloHTTPS en el puerto 443, sin usuario ni contraseña en la URL
CertificadoVigente, emitido por una autoridad reconocida y válido para tu dominio. Uno autofirmado o vencido cuenta como fallo
DirecciónTu dominio debe resolver a direcciones IPv4 públicas. Las entregas salen sólo por IPv4
ConexiónHasta 5 segundos para abrirla
Respuesta10 segundos para que FITAL reciba tu código de estado
ÉxitoCualquier 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
RedireccionesNo 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:

EncabezadoContenido
X-Fital-TimestampSegundos unix de este intento. Cambia en cada reintento
X-Fital-SignatureUna o más firmas v1=<hex>, separadas por comas
X-Fital-Event-IdIdentificador del evento. Sirve para depurar y no va firmado: para deduplicar usa el id del cuerpo
X-Fital-Delivery-IdIdentificador 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.

  1. 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.
  2. Guardar el secreto. El panel muestra el secreto de firma de ese destino una sola vez. Guárdalo en tu gestor de secretos.
  3. Verificar. Implementa Verificar la firma en tu receptor, con ese secreto.
  4. Probar. Abre Ver entregas y elige Enviar evento de prueba. Llega un evento webhook.test, con {"test": true} en data, y el resultado aparece en esa misma lista. Ese evento no entra a GET /v1/events.
  5. Activar. No hay un paso aparte: el destino nace active y recibe eventos desde que lo creas. Créalo cuando tu receptor ya esté listo.

Estados de un destino

EstadoSignificaQué puedes hacer
activeRecibe eventosProbar, reenviar entregas, rotar el secreto y eliminarlo
disabledDejó de recibir eventos tras 3 días seguidos de fallosReactivar, rotar el secreto y eliminarlo. El panel rechaza pruebas y reenvíos hasta reactivarlo
pausedValor reservado. Hoy ningún flujo lo asignaTrá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.

ResultadoEstado de la entrega
EntregadoTu receptor respondió 2xx
FallóSigue reintentando, o quedó exhausted
DescartadoQuedó skipped, sin entrega
PendienteAú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:

  1. Pide la lista sin cursor: empieza en el evento más nuevo.
  2. Sigue con next_cursor hacia el pasado hasta llegar a un evento que ya procesaste, o hasta que next_cursor sea null.
  3. Procesa lo nuevo y guarda los id vistos o la hora del último.
  4. 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.

eventos.sh
# 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 el id del 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 el id del cuerpo.
  • Responder 2xx hasta terminar de procesar: todo tu trabajo tendría que caber en 10 segundos. Guarda el evento, responde y procesa después.
  • Guardar next_cursor de GET /v1/events para 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.

Qué sigue

Verificar la firma y Catálogo de eventos.

Ver esta página en Markdown

En esta página