Webhooks
Los webhooks de FactuLink te permiten recibir notificaciones HTTP en tiempo real cuando ocurren eventos importantes en tu cuenta, como el timbrado o cancelacion de un CFDI.
En lugar de hacer polling constante a la API, configuras una URL en tu servidor y
FactuLink envia un POST con el detalle del evento cada vez que algo sucede.
Disponible en produccion — base URL
https://api.factulink.com.mx. Requiere una API Key (sk_test_...osk_live_...) o un JWT con rol ADMIN.
Eventos disponibles
| Evento | Estado | Descripcion |
|---|---|---|
cfdi.timbrado | Vivo | El CFDI fue timbrado exitosamente por el PAC |
cfdi.cancelado | Vivo | El CFDI fue cancelado ante el SAT |
cfdi.fallido | Vivo | El timbrado de un CFDI fallo (error de PAC o validacion) |
cfdi.encolado | Vivo | El CFDI fue encolado para timbrado asincrono |
csd.por_vencer | Planeado | Un CSD esta proximo a vencer (30 dias antes) |
csd.vencido | Planeado | Un CSD ha vencido y ya no puede firmar CFDIs |
Registrar un webhook
curl -X POST https://api.factulink.com.mx/api/v1/webhooks \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://tu-servidor.com/fc-webhook",
"events": ["cfdi.timbrado", "cfdi.cancelado"]
}'La respuesta incluye el secret una sola vez — guardalo de inmediato, no se
puede recuperar despues:
{
"id": "9c2b1f3e-4d5a-6b7c-8d9e-0f1a2b3c4d5e",
"url": "https://tu-servidor.com/fc-webhook",
"events": ["cfdi.timbrado", "cfdi.cancelado"],
"secret": "whsec_3f8b...",
"estado": "ACTIVO"
}Endpoints disponibles
| Metodo | Ruta | Descripcion |
|---|---|---|
POST | /api/v1/webhooks | Crear webhook (devuelve secret una sola vez) |
GET | /api/v1/webhooks | Listar webhooks del tenant |
PATCH | /api/v1/webhooks/:id | Actualizar url, events o estado |
DELETE | /api/v1/webhooks/:id | Eliminar webhook |
GET | /api/v1/webhooks/:id/deliveries | Historial de entregas |
POST | /api/v1/webhooks/:id/test | Disparar evento de prueba |
Formato del payload
Cada webhook envia un POST con un payload JSON con la siguiente estructura:
{
"event": "cfdi.timbrado",
"tenant_id": "01HQ3KD5R8N2YPTM4VBWG7E9CK",
"created_at": "2026-04-07T10:35:12.000Z",
"data": {
"cfdi_id": "f1e2d3c4-b5a6-7980-1234-567890abcdef",
"uuid": "6128396f-c09c-4e3a-b4d7-8a5f2e1c9b3d"
}
}El contenido de data varia por tipo de evento:
| Evento | Campos en data |
|---|---|
cfdi.timbrado | cfdi_id, uuid |
cfdi.cancelado | cfdi_id, uuid, motivo, estado |
cfdi.fallido | cfdi_id |
cfdi.encolado | cfdi_id |
Headers enviados
Cada request incluye los siguientes headers:
| Header | Valor |
|---|---|
Content-Type | application/json |
X-FC-Event | El nombre del evento (ej. cfdi.timbrado) |
X-FC-Signature | sha256=<hex> HMAC-SHA256 del body usando tu webhook secret |
X-FC-Delivery-Id | UUID unico de la entrega (idempotencia) |
X-FC-Timestamp | Unix timestamp en segundos |
Verificacion de firma
X-FC-Signature contiene una firma HMAC-SHA256 del body crudo usando tu
webhook secret. Verificala antes de confiar en el payload:
import crypto from 'node:crypto';
function verifySignature(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf-8')
.digest('hex');
const expectedHeader = `sha256=${expected}`;
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expectedHeader),
);
}Importante: Usa el body crudo (raw bytes) tal como llego en el request, no el JSON re-serializado. Cualquier cambio de espacios o orden de claves rompe la firma.
Reintentos y entrega
- Timeout: 10 segundos por intento.
- Exito: cualquier respuesta
2xxmarca la entrega como exitosa. - Reintentos: las entregas fallidas se reintentan automáticamente con backoff exponencial.
- Historial: consulta
GET /api/v1/webhooks/:id/deliveriespara ver el estado, status code y body de respuesta de cada entrega. - Auto-pausa: si un endpoint acumula 10 fallas consecutivas (campo
consecutive_failures), se pausa automaticamente. Reactivalo conPATCH /api/v1/webhooks/:idcambiandoestadoaACTIVO. - Idempotencia: trata cada
X-FC-Delivery-Idcomo único — si recibes el mismo delivery dos veces (por reintento), procesa el evento una sola vez.
Probar tu integracion
Usa el endpoint de prueba para disparar un evento sintetico sin esperar a que ocurra uno real:
curl -X POST https://api.factulink.com.mx/api/v1/webhooks/<webhook-id>/test \
-H "Authorization: Bearer sk_test_..."SDK Node.js
Si usas @factulink/node, los webhooks se administran con el resource
fc.webhooks:
import FactuLink from '@factulink/node';
const fc = new FactuLink({ apiKey: process.env.FC_API_KEY! });
const { data: webhook } = await fc.webhooks.create({
url: 'https://tu-servidor.com/fc-webhook',
events: ['cfdi.timbrado', 'cfdi.cancelado'],
});
console.log('Guarda este secret:', webhook.secret);Consulta la referencia del SDK Node.js para mas detalles.