Webhooks
Un webhook es una URL https de tu sistema que recibe por POST los eventos de tu cuenta, firmados con su secreto. Con estos endpoints lo das de alta, lo cambias, lo pruebas, rotas su secreto y consultas el log de sus entregas.
Revisa la introducción a los webhooks para conocer los tipos de evento, la verificación de la firma, los reintentos y la rotación del secreto.
Fechas y errores
Administrar webhooks requiere el permiso Webhooks.Manage, que tienen el owner y los miembros de la cuenta. La API acepta tu API key o la sesión del dashboard.
Las fechas de los webhooks y de las entregas vienen en la zona del header X-TIME-ZONE, sin offset y con el formato yyyy-MM-ddTHH:mm:ss.fff; sin ese header, vienen en UTC con Z final. El sobre de los eventos viene siempre en UTC con Z final, también en Listar eventos.
En una respuesta de error 4xx, message es "Sorry, it's not me, it's you." salvo en el 429, y la causa va en details, o en data si es una falla de validación. Sin una API key o una sesión válidas, la API responde 401; sin el permiso Webhooks.Manage, responde 403 con "No tienes permiso para administrar webhooks.".
Probar webhook, Reenviar evento y Disparar evento de prueba tienen un límite de solicitudes por API key. Si lo excedes, la API responde 429 con el header Retry-After, en segundos, y no ejecuta la operación:
Response
{
"data": null,
"succeeded": false,
"message": "Too Many Requests",
"details": "Se excedió el límite de solicitudes de la familia BusinessWrite (600 por 60s). Reintenta en 12 segundos. Los headers RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset indican tu consumo actual en cada respuesta.",
"httpStatusCode": 429,
"traceIdentifier": "8D2F6A41C07B4E9DA35E1F0B6C7E2A93"
}
Crear webhook
Este endpoint da de alta una URL que recibe los eventos suscritos de tu cuenta, o de las personas que indiques. Responde 201 con el webhook y su secreto de firma.
Modelo
- Name
url- Type
- string
- required
- Description
URL https que recibe las entregas. Ver las reglas en Registrar un webhook.
- Name
description- Type
- string?
- Description
Descripción de hasta 256 caracteres.
- Name
enabledEvents- Type
- array of strings
- required
- Description
Tipos de evento que recibe, o solo
"*"para todos. Ver Tipos de evento.
- Name
personIds- Type
- array of strings?
- Description
Ids de las personas cuyos eventos recibe, hasta 100. Omitido o vacío, recibe los de todas.
Respuesta
- Name
id- Type
- string?
- Description
Id del webhook.
- Name
kind- Type
- string?
- Description
"url"para un webhook de URL.
- Name
url- Type
- string?
- Description
URL que recibe las entregas.
- Name
description- Type
- string?
- Description
Descripción del webhook, o
""si no tiene.
- Name
enabledEvents- Type
- array of strings?
- Description
Tipos de evento suscritos, o solo
"*".
- Name
personIds- Type
- array of strings?
- Description
Personas cuyos eventos recibe, o
nullsi recibe los de todas.
- Name
status- Type
- string?
- Description
"enabled"o"disabled".
- Name
disabledReason- Type
- string?
- expandible
- Description
Por qué está deshabilitado, o
null.- Type
- enum:
- Values
- "manual""gone""failingTooLong"
- Name
disabledAt- Type
- string?
- Description
Cuándo se deshabilitó, o
null.
- Name
failingSince- Type
- string?
- Description
Desde cuándo falla el webhook, o
nullsi no está fallando. Solo lo limpia el 2xx de un evento real, no el de una prueba.
- Name
listenerPersonId- Type
- string?
- Description
Solo para las escuchas del CLI. En un webhook de URL es
null.
- Name
sessionExpiresAt- Type
- string?
- Description
Solo para las escuchas del CLI. En un webhook de URL es
null.
- Name
createdAt- Type
- string?
- Description
Fecha y hora del alta.
- Name
updatedAt- Type
- string?
- Description
Fecha y hora del último cambio. Al crear es la misma que
createdAt.
- Name
secret- Type
- string?
- Description
Secreto de firma:
whsec_seguido de 44 caracteres en base64. Guárdalo: con la API key, solo se responde al crear y al rotar.
Errores
Una falla de validación llega en data como ValidationFailure, con el campo que la causó; las demás llegan con el mensaje en details. En el mensaje del tipo, <tipo> es el tipo enviado.
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 400 | Falta la URL. Llega como falla de validación en Url. | La URL es obligatoria. |
| 400 | La URL pasa de 2048 caracteres. Llega como falla de validación en Url. | La URL no puede tener más de 2048 caracteres. |
| 400 | La URL no es https, no es absoluta o lleva usuario y contraseña. Llega como falla de validación en Url. | La URL del webhook debe ser https, absoluta y sin usuario ni contraseña. |
| 400 | La URL apunta a una dirección privada, local o reservada, o a localhost. Llega como falla de validación en Url. | La URL del webhook apunta a una dirección no permitida. |
| 400 | La descripción pasa de 256 caracteres. Llega como falla de validación en Description. | La descripción no puede tener más de 256 caracteres. |
| 400 | No hay tipos de evento. Llega como falla de validación en EnabledEvents. | Indica al menos un tipo de evento o '*' para recibirlos todos. |
| 400 | El comodín va con otros tipos. Llega como falla de validación en EnabledEvents. | El comodín '*' no se combina con otros tipos de evento. |
| 400 | Un tipo de evento no existe. Llega como falla de validación en el elemento de EnabledEvents, por ejemplo EnabledEvents[1]. | El tipo de evento '<tipo>' no existe. |
| 400 | Son más de 100 personas. Llega como falla de validación en PersonIds. | Un webhook filtra como máximo 100 personas. |
| 400 | Una persona no es de tu cuenta. Llega en details. | La persona especificada no es válida o no pertenece a tu organización. |
| 400 | Tu cuenta ya tiene 16 webhooks de URL. Llega en details. | Se alcanzó el máximo de 16 webhooks. |
| 409 | Otro cambio a tus webhooks está en curso. Repite la solicitud. Llega en details. | Otro cambio a los webhooks está en curso; intenta de nuevo. |
Request
curl --location 'https://test.fiscalapi.com/api/v4/webhooks' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api_key>' \
--data-raw '{
"url": "https://www.example.com/webhooks/fiscalapi",
"description": "ERP producción",
"enabledEvents": ["invoice.canceled", "invoice.cancellation_rejected"],
"personIds": ["4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b"]
}'
Response
{
"data": {
"secret": "whsec_ZWplbXBsby1kZS1zZWNyZXRvLWRlLWZpcm1hLTAwMDE=",
"id": "6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0",
"kind": "url",
"url": "https://www.example.com/webhooks/fiscalapi",
"description": "ERP producción",
"enabledEvents": ["invoice.canceled", "invoice.cancellation_rejected"],
"personIds": ["4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b"],
"status": "enabled",
"disabledReason": null,
"disabledAt": null,
"failingSince": null,
"listenerPersonId": null,
"sessionExpiresAt": null,
"createdAt": "2026-10-13T09:15:00.000",
"updatedAt": "2026-10-13T09:15:00.000"
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 201
}
Listar webhooks
Este endpoint devuelve los webhooks de tu cuenta en orden de alta, paginados y sin su secreto. Cada elemento tiene las propiedades de la respuesta de Crear webhook, salvo secret.
El listado también incluye las escuchas de fiscalapi listen, con kind en "cliListener" y la URL en null. Cada escucha solo la ven su persona y el owner de la cuenta, y para los demás no existe.
Para una grid, GET /api/v4/webhooks/load devuelve los mismos webhooks con las opciones de carga de DevExtreme (filtro, orden, paginación y agrupación) y responde su LoadResult, sin el sobre de la respuesta. Valida sus parámetros como las demás consultas load; ver ValidationFailure.
Query parameters
- Name
pageNumber- Type
- int?
- Description
Número de página.
Default:1
- Name
pageSize- Type
- int?
- Description
Webhooks por página, de 1 a 50.
Default:10
Errores
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 400 | pageSize está fuera de 1 a 50. Llega como falla de validación en pageSize. | 'pageSize' debe estar entre 1 y 50. |
| 400 | pageNumber está fuera de rango. Llega como falla de validación en pageNumber. | 'pageNumber' debe estar entre 1 y 42949672. |
Request
curl --location 'https://test.fiscalapi.com/api/v4/webhooks?pageNumber=1&pageSize=10' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"items": [
{
"id": "6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0",
"kind": "url",
"url": "https://www.example.com/webhooks/fiscalapi",
"description": "ERP producción",
"enabledEvents": ["invoice.canceled", "invoice.cancellation_rejected"],
"personIds": ["4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b"],
"status": "enabled",
"disabledReason": null,
"disabledAt": null,
"failingSince": null,
"listenerPersonId": null,
"sessionExpiresAt": null,
"createdAt": "2026-10-13T09:15:00.000",
"updatedAt": "2026-10-13T09:15:00.000"
}
],
"pageNumber": 1,
"totalPages": 1,
"totalCount": 1,
"hasPreviousPage": false,
"hasNextPage": false
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Obtener webhook
Este endpoint devuelve un webhook de tu cuenta con su estado, sin su secreto. Tiene las propiedades de la respuesta de Crear webhook, salvo secret.
Errores
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 404 | El webhook no existe en tu cuenta. Llega en details. | No such resource for the given Id: <id> |
| 404 | El id no es un GUID. Llega en details. | Not Found |
Request
curl --location 'https://test.fiscalapi.com/api/v4/webhooks/6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"id": "6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0",
"kind": "url",
"url": "https://www.example.com/webhooks/fiscalapi",
"description": "ERP producción",
"enabledEvents": ["invoice.canceled", "invoice.cancellation_rejected"],
"personIds": ["4f6a2c1e-8b3d-4e5f-9a7b-1c2d3e4f5a6b"],
"status": "enabled",
"disabledReason": null,
"disabledAt": null,
"failingSince": null,
"listenerPersonId": null,
"sessionExpiresAt": null,
"createdAt": "2026-10-13T09:15:00.000",
"updatedAt": "2026-10-13T09:15:00.000"
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Reemplazar webhook
Este endpoint reemplaza la URL, la descripción, los tipos de evento y las personas de un webhook, y con status lo habilita o lo deshabilita. Envía el modelo completo, con el id del webhook igual al de la ruta.
Habilitar un webhook deshabilitado limpia failingSince, disabledReason y disabledAt. Deshabilitarlo cierra sus entregas pendientes. Responde el webhook sin su secreto.
Modelo
- Name
id- Type
- string
- required
- Description
Id del webhook, el mismo de la ruta.
- Name
url- Type
- string
- required
- Description
URL https que recibe las entregas.
- Name
description- Type
- string?
- Description
Descripción de hasta 256 caracteres.
- Name
enabledEvents- Type
- array of strings
- required
- Description
Tipos de evento que recibe, o solo
"*".
- Name
personIds- Type
- array of strings?
- Description
Personas cuyos eventos recibe. Omitido o vacío, recibe los de todas.
- Name
status- Type
- string?
- Description
"enabled"o"disabled". Omitido, conserva el estado.
Errores
Responde los mismos errores de URL, descripción, tipos y personas de Crear webhook, en el mismo lugar de la respuesta, salvo el tope de 16 webhooks. Además:
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 400 | El id del cuerpo no es el de la ruta. Llega como falla de validación en id. | IDs mismatches |
| 400 | El webhook es una escucha del CLI. Llega en details. | Una escucha de CLI no se edita ni se prueba; usa fiscalapi listen y fiscalapi trigger. |
| 404 | El webhook no existe en tu cuenta. Llega en details. | No such resource for the given Id: <id> |
| 404 | El id no es un GUID. Llega en details. | Not Found |
| 409 | Otro cambio a tus webhooks está en curso. Repite la solicitud. Llega en details. | Otro cambio a los webhooks está en curso; intenta de nuevo. |
Request
curl --location --request PUT 'https://test.fiscalapi.com/api/v4/webhooks/6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api_key>' \
--data-raw '{
"id": "6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0",
"url": "https://www.example.com/webhooks/fiscalapi",
"description": "ERP producción",
"enabledEvents": ["*"],
"personIds": [],
"status": "enabled"
}'
Response
{
"data": {
"id": "6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0",
"kind": "url",
"url": "https://www.example.com/webhooks/fiscalapi",
"description": "ERP producción",
"enabledEvents": ["*"],
"personIds": null,
"status": "enabled",
"disabledReason": null,
"disabledAt": null,
"failingSince": null,
"listenerPersonId": null,
"sessionExpiresAt": null,
"createdAt": "2026-10-13T09:15:00.000",
"updatedAt": "2026-10-13T10:02:31.000"
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Eliminar webhook
Este endpoint da de baja un webhook, de URL o una escucha de fiscalapi listen, y cierra sus entregas pendientes. Su log de entregas se conserva y lo sigues consultando con Listar entregas.
Errores
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 404 | El webhook no existe en tu cuenta. Llega en details. | No such resource for the given Id: <id> |
| 404 | El id no es un GUID. Llega en details. | Not Found |
| 409 | Otro cambio a tus webhooks está en curso. Repite la solicitud. Llega en details. | Otro cambio a los webhooks está en curso; intenta de nuevo. |
Request
curl --location --request DELETE 'https://test.fiscalapi.com/api/v4/webhooks/6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": true,
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Probar webhook
Este endpoint registra un evento webhook.test y lo entrega solo a este webhook, firmado y en un solo intento, sin reintentos. Su 2xx no cuenta para la salud del webhook; ver Probar tu receptor. Responde 202 con los ids del evento y de la entrega; sigue el resultado en Listar entregas.
Respuesta
- Name
eventId- Type
- string?
- Description
Id del evento de prueba.
- Name
deliveryId- Type
- string?
- Description
Id de la entrega en el log del webhook.
Errores
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 400 | El webhook está deshabilitado. Llega en details. | El webhook está deshabilitado: habilítalo antes de probarlo. |
| 400 | El webhook es una escucha del CLI. Llega en details. | Una escucha de CLI no se edita ni se prueba; usa fiscalapi listen y fiscalapi trigger. |
| 404 | El webhook no existe en tu cuenta. Llega en details. | No such resource for the given Id: <id> |
| 404 | El id no es un GUID. Llega en details. | Not Found |
| 429 | Excediste el límite de solicitudes de tu API key. Llega en message, con el detalle en details y el header Retry-After. Ver Límite de solicitudes. | Too Many Requests |
Request
curl --location --request POST 'https://test.fiscalapi.com/api/v4/webhooks/6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0/test' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"eventId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"deliveryId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f"
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 202
}
Rotar secreto
Este endpoint genera un secreto de firma nuevo y lo responde en secret, junto con el webhook. El secreto anterior sigue firmando cada entrega durante 24 horas. Ver Rotar el secreto.
Errores
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 404 | El webhook no existe en tu cuenta. Llega en details. | No such resource for the given Id: <id> |
| 404 | El id no es un GUID. Llega en details. | Not Found |
| 409 | Otro cambio a tus webhooks está en curso. Repite la solicitud. Llega en details. | Otro cambio a los webhooks está en curso; intenta de nuevo. |
Request
curl --location --request POST 'https://test.fiscalapi.com/api/v4/webhooks/6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0/rotate-secret' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"secret": "whsec_ZWplbXBsby1kZS1zZWNyZXRvLXJvdGFkby0wMDAwMDI=",
"id": "6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0",
"kind": "url",
"url": "https://www.example.com/webhooks/fiscalapi",
"description": "ERP producción",
"enabledEvents": ["*"],
"personIds": null,
"status": "enabled",
"disabledReason": null,
"disabledAt": null,
"failingSince": null,
"listenerPersonId": null,
"sessionExpiresAt": null,
"createdAt": "2026-10-13T09:15:00.000",
"updatedAt": "2026-10-13T11:40:12.000"
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Listar entregas
Este endpoint devuelve el log de entregas de un webhook, de la más nueva a la más vieja, también si el webhook ya se dio de baja. Solo incluye entregas de eventos de los últimos 30 días.
Para una grid, GET /api/v4/webhooks/<id>/deliveries/load devuelve las mismas entregas con las opciones de carga de DevExtreme y responde su LoadResult, sin el sobre de la respuesta; ver ValidationFailure.
Query parameters
- Name
pageNumber- Type
- int?
- Description
Número de página.
Default:1
- Name
pageSize- Type
- int?
- Description
Entregas por página, de 1 a 50.
Default:10
Respuesta
- Name
id- Type
- string?
- Description
Id de la entrega.
- Name
createdAt- Type
- string?
- Description
Cuándo se creó la entrega.
- Name
eventId- Type
- string?
- Description
Id del evento entregado. Lo consultas con Obtener evento.
- Name
kind- Type
- string?
- expandible
- Description
Qué originó la entrega.
- Type
- enum:
- Values
- "automatic""manual""test"
- Name
status- Type
- string?
- expandible
- Description
Estado de la entrega.
- Type
- enum:
- Values
- "pending""succeeded""failed"
- Name
attempts- Type
- int?
- Description
Intentos hechos.
- Name
lastStatusCode- Type
- int?
- Description
Código HTTP del último intento, o
nullsi no hubo respuesta. Un timeout al leer el cuerpo de la respuesta conserva el código que alcanzó a llegar.
- Name
lastError- Type
- string?
- expandible
- Description
Por qué el último intento no obtuvo una respuesta completa, o por qué la entrega se cerró sin enviarse;
nullsi hubo respuesta completa. Trátalo como texto libre: puede tener otros valores. En una escucha del CLI es el texto del error que reportó el CLI, de hasta 64 caracteres. Estos son los valores de hoy:- Type
- enum:
- Values
- "Timeout""ConnectionFailed""TlsFailed"
- Name
lastResponseExcerpt- Type
- string?
- Description
Primer KB de la última respuesta del receptor.
- Name
eventType- Type
- string?
- Description
Tipo del evento entregado.
- Name
nextAttemptAt- Type
- string?
- Description
Cuándo toca el siguiente intento. Solo tiene sentido mientras
statuses"pending"; en una entrega terminada, ignóralo.
- Name
lastAttemptAt- Type
- string?
- Description
Cuándo se hizo el último intento, o
null.
- Name
lastLatencyMs- Type
- int?
- Description
Milisegundos que tardó el último intento, o
null.
Errores
| Estado | Cuándo ocurre | Mensaje |
|---|---|---|
| 400 | pageSize está fuera de 1 a 50. Llega como falla de validación en pageSize. | 'pageSize' debe estar entre 1 y 50. |
| 400 | pageNumber está fuera de rango. Llega como falla de validación en pageNumber. | 'pageNumber' debe estar entre 1 y 42949672. |
| 404 | El webhook no existe en tu cuenta, ni dado de baja. Llega en details. | No such resource for the given Id: <id> |
| 404 | El id no es un GUID. Llega en details. | Not Found |
Request
curl --location 'https://test.fiscalapi.com/api/v4/webhooks/6f7a8b9c-0d1e-4f2a-b3c4-d5e6f7a8b9c0/deliveries?pageNumber=1&pageSize=10' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"items": [
{
"id": "e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8",
"eventId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"eventType": "invoice.canceled",
"kind": "automatic",
"status": "succeeded",
"attempts": 2,
"nextAttemptAt": "2026-10-12T12:47:58.000",
"lastAttemptAt": "2026-10-12T12:48:41.000",
"lastStatusCode": 200,
"lastLatencyMs": 184,
"lastError": null,
"lastResponseExcerpt": "{\"received\":true}",
"createdAt": "2026-10-12T12:47:53.000"
}
],
"pageNumber": 1,
"totalPages": 1,
"totalCount": 1,
"hasPreviousPage": false,
"hasNextPage": false
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}