Webhooks
Los webhooks notifican en tiempo real cuando ocurren cambios en los recursos de tu empresa. En vez de hacer polling, configuras una URL HTTPS y FacturaDirecta envía una petición POST con el detalle del cambio cada vez que se produce un evento al que estás suscrito.
En los ejemplos de esta página, los UUIDs (whe_…, con_…, inv_…) y los signing_secret son ilustrativos. Cada endpoint tiene los suyos; sustitúyelos por los valores reales que devuelve la API.
Dos sub-recursos
El recurso webhooks se divide en dos partes:
Endpoints (
/webhooks/endpoints/...) — la configuración: URLs destino, eventos a los que se suscriben, activación, rotación del secret de firma.Eventos (
/webhooks/events/...) — la historia de entregas: cada cambio en un recurso genera un evento por cada endpoint suscrito. Permite inspeccionar qué se envió, su estado y reintentar entregas fallidas.
Cada endpoint se suscribe a uno o más tipos de evento. Formato: <recurso>.<acción>. La lista completa de tipos disponibles está más abajo:
Catálogo de eventos
Eventos disponibles para suscripción en events al crear o actualizar un endpoint:
Recurso | Eventos |
Facturas |
|
Gastos |
|
Contactos |
|
Presupuestos |
|
Albaranes |
|
Transacciones |
|
Productos |
|
VeriFactu |
|
Bandeja de entrada |
|
El catálogo puede ampliarse ante cambios; no asumas un conjunto cerrado. Para suscribirte a todos los eventos de una categoría, declara cada tipo explícitamente.
Forma del payload entregado
Cuando se dispara un evento, FacturaDirecta envía un POST a la URL del endpoint con este cuerpo JSON:
{
"id": "whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d",
"type": "invoice.created",
"object_type": "invoice",
"created": 1715616000,
"livemode": true,
"company_id": "com_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3",
"data": {
"object": { "...": "snapshot del recurso afectado" },
"previous_attributes": { "...": "diff con el estado anterior (solo en eventos *.updated)" }
}
}
Notas:
id(prefijowhe_) identifica la entrega. Es el mismo valor que eliddel evento enGET /webhooks/events/{id}.typecoincide con uno de los tipos del catálogo.object_typees el tipo de recurso afectado (invoice,contact...).createdes timestamp Unix en segundos.livemode: trueindica que es un evento real;falsecorresponde a eventos simulados (sandbox o reintentos manuales).data.objectcontiene el snapshot completo del recurso en el momento del evento.data.previous_attributesaparece solo en eventos*.updatedy contiene un diff con los valores anteriores de los campos modificados.
Firma del payload
Cada petición incluye dos cabeceras HTTP para que verifiques que el payload viene realmente de FacturaDirecta:
Webhook-Signature: t=1715616000,v1=8f3d4e7c2b9a1f5e6d0c8b7a3e5f9d1c2b4a6e8f0d3c5b7a9e1f3d5c7b9a0e2f Webhook-Timestamp: 1715616000
Algoritmo, equivalente al de Stripe:
Toma el
Webhook-Timestampy el cuerpo crudo de la petición.Concatena:
signed_payload = "<timestamp>.<raw_body>".Calcula
HMAC-SHA256(secret, signed_payload)y compáralo con el valorv1=...del headerWebhook-Signature.Rechaza la petición si la diferencia entre
Webhook-Timestampy la hora actual supera 5 minutos (anti-replay).
El secret es el signing_secret devuelto al crear el endpoint (formato whsec_<hex>) o tras rotateSecret. Solo se muestra una vez en la creación/rotación. Guárdalo en un lugar seguro al recibirlo.
Política de reintentos
Si tu endpoint responde con un código distinto de 2xx o no responde, FacturaDirecta reintenta la entrega con backoff exponencial. Calendario hardcoded:
Intento | Espera desde el anterior | Tiempo total desde el primer envío |
1 | — | 0 |
2 | 15 segundos | 15 s |
3 | 45 segundos | 1 min |
4 | 2 minutos | 3 min |
5 | 5 minutos | 8 min |
6 | 15 minutos | 23 min |
7 | 45 minutos | 1 h 8 min |
8 | 2 horas | 3 h 8 min |
9 | 5 horas | 8 h 8 min |
10 | 10 horas | 18 h 8 min |
11 | 18 horas | 36 h 8 min |
12 | 36 horas | ~72 horas |
Tras 12 intentos sin éxito el evento queda en estado failed. Puedes reintentar manualmente con POST /webhooks/events/{id}/retry.
Operaciones
Endpoints
Crear endpoint
POST /{companyId}/webhooks/endpoints crea un endpoint con su lista inicial de suscripciones.
Parámetros del body:
name(obligatorio) — nombre descriptivo del endpoint.url(obligatorio) — URL HTTPS de destino.events(obligatorio) — array de tipos de evento del catálogo.
Respuesta: { content: <endpoint>, signing_secret: "whsec_<hex>" }. El signing_secret solo se muestra en la creación; guárdalo en seguro.
Ejemplo de request JSON
{
"name": "Integración ERP — facturas",
"url": "https://api.miempresa.com/webhooks/facturadirecta",
"events": ["invoice.created", "invoice.updated", "invoice.voided"]
}
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "Integración ERP — facturas",
"url": "https://api.miempresa.com/webhooks/facturadirecta",
"events": ["invoice.created", "invoice.updated", "invoice.voided"]
}' \
"https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints"
Listar endpoints
GET /{companyId}/webhooks/endpoints devuelve los endpoints configurados en la empresa.
Respuesta: { items: WebhookEndpoint[] }. Sin paginación (ver patrón sin paginación en la guía de paginación).
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints"
Obtener un endpoint
GET /{companyId}/webhooks/endpoints/{endpointId} devuelve un endpoint por ID.
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d"
Actualizar endpoint
PUT /{companyId}/webhooks/endpoints/{endpointId} aplica una actualización parcial: los campos omitidos conservan su valor actual. A diferencia de los recursos documento, no es un reemplazo completo.
Parámetros del body (todos opcionales):
nameurlevents(la lista nueva reemplaza la anterior).
Ejemplo de request JSON
Cambiar solo la lista de eventos a los que se suscribe:
{
"events": ["invoice.created", "invoice.voided"]
}
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \
-d '{"events":["invoice.created","invoice.voided"]}' \
"https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d"
Borrar endpoint
DELETE /{companyId}/webhooks/endpoints/{endpointId} elimina el endpoint. Las entregas pendientes y el historial de eventos asociados quedan disponibles en /webhooks/events para consulta, pero no se reintentan tras el borrado.
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d"
Activar endpoint
POST /{companyId}/webhooks/endpoints/{endpointId}/enable reactiva un endpoint previamente desactivado. Solo afecta a eventos futuros; no reintenta entregas pasadas (para eso, ver Reintentar un evento).
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/enable"
Desactivar endpoint
POST /{companyId}/webhooks/endpoints/{endpointId}/disable deja al endpoint sin recibir eventos nuevos. Los eventos pendientes en cola no se enviarán.
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/disable"
Rotar secret de firma
POST /{companyId}/webhooks/endpoints/{endpointId}/rotateSecret genera un nuevo signing_secret para el endpoint e invalida el anterior.
Respuesta: { signing_secret: "whsec_<hex>" }. Solo se muestra una vez; guárdalo en seguro antes de actualizar tu integración.
Estrategia recomendada: rotar en horario de baja actividad. Mientras actualizas tu integración, los eventos firmados con el secret antiguo fallarán hasta que verifiques con el nuevo.
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/endpoints/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/rotateSecret"
Eventos
Listar eventos
GET /{companyId}/webhooks/events devuelve el historial de entregas con paginación por cursor (ver el patrón B en la guía de paginación).
Parámetros de consulta:
endpointId— filtrar por endpoint.type— filtrar por tipo de evento (p. ej.invoice.created).status— filtrar por estado:pending,delivered,failed.limit— máximo100.cursor—created_atdel último evento de la página anterior, en formato ISO 8601 UTC. Omitir para la primera página.
Respuesta: { items: WebhookEvent[], hasMore: boolean }. El cursor para la siguiente página es el created_at del último elemento.
Copy as cURL
Primera página:
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events?status=failed&limit=100"
Siguiente página:
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events?status=failed&limit=100&cursor=2026-04-15T10:22:31.000Z"
Obtener un evento
GET /{companyId}/webhooks/events/{eventId} devuelve un evento por ID con su detalle (incluyendo el payload enviado y el resultado del último intento).
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d"
Reintentar un evento
POST /{companyId}/webhooks/events/{eventId}/retry encola una nueva entrega del evento contra el endpoint configurado.
Útil para:
Eventos en estado
failedcuando ya has solucionado el problema en tu integración.Eventos
deliveredcuando necesitas re-procesarlos en tu sistema (la API no impide reintentar entregas exitosas).
Copy as cURL
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \ -X POST \ "https://app.facturadirecta.com/api/$COMPANY_ID/webhooks/events/whe_5a8b1c34-2d4e-4f6a-8b9c-1e3f5a7b9c0d/retry"
Recomendaciones para integradores
Verifica siempre la firma antes de procesar el payload. No confíes en el remitente del header
Hostni en certificados TLS; el secret HMAC es la única garantía.Responde rápido (2xx) para evitar reintentos. Si necesitas procesamiento largo, encola el evento en tu lado y responde
200inmediatamente.Asume entregas duplicadas. Tu integración debe ser idempotente: el
iddel evento es estable y sirve para deduplicar.Trata
event.idcomo llave primaria. El campoid(whe_...) es único por entrega y persiste entre reintentos.Para sincronizaciones desde cero, en vez de paginar todo el historial guarda el
created_atdel último evento procesado con éxito y reanuda desde ahí.Tras rotar el secret, soporta brevemente ambos secrets (antiguo y nuevo) hasta que confirmes que todos los eventos en vuelo se firmaron con el nuevo.
Errores comunes
400 ValidationError—urlno es HTTPS oeventscontiene un tipo desconocido.404 Not Found— elendpointIdoeventIdno existe en la empresa.409 Conflict— intento de rotar secret en un endpoint borrado.
Ver Errores y validaciones para el formato general.
Endpoints
Método | Path | operationId | Scopes | Descripción |
GET |
|
|
| Lista de endpoints de webhooks |
GET |
|
|
| Obtener endpoint de webhook |
GET |
|
|
| Lista de eventos de webhook |
GET |
|
|
| Obtener evento de webhook |
POST |
|
|
| Crear endpoint de webhook |
POST |
|
|
| Desactivar endpoint de webhook |
POST |
|
|
| Activar endpoint de webhook |
POST |
|
|
| Rotar secret de firma del endpoint |
POST |
|
|
| Reintentar entrega de evento |
PUT |
|
|
| Actualizar endpoint de webhook |
DELETE |
|
|
| Eliminar endpoint de webhook |
Scopes
webhooks:read— Leer configuración y historial de webhooks.webhooks:write— Gestionar endpoints de webhooks.