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.

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"
}

POST/api/v4/webhooks

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 null si 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 null si 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.

EstadoCuándo ocurreMensaje
400Falta la URL. Llega como falla de validación en Url.La URL es obligatoria.
400La URL pasa de 2048 caracteres. Llega como falla de validación en Url.La URL no puede tener más de 2048 caracteres.
400La 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.
400La 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.
400La 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.
400No hay tipos de evento. Llega como falla de validación en EnabledEvents.Indica al menos un tipo de evento o '*' para recibirlos todos.
400El 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.
400Un 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.
400Son más de 100 personas. Llega como falla de validación en PersonIds.Un webhook filtra como máximo 100 personas.
400Una persona no es de tu cuenta. Llega en details.La persona especificada no es válida o no pertenece a tu organización.
400Tu cuenta ya tiene 16 webhooks de URL. Llega en details.Se alcanzó el máximo de 16 webhooks.
409Otro 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

POST
/api/v4/webhooks
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
}

GET/api/v4/webhooks

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

EstadoCuándo ocurreMensaje
400pageSize está fuera de 1 a 50. Llega como falla de validación en pageSize.'pageSize' debe estar entre 1 y 50.
400pageNumber está fuera de rango. Llega como falla de validación en pageNumber.'pageNumber' debe estar entre 1 y 42949672.

Request

GET
/api/v4/webhooks?pageNumber=1&pageSize=10
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
}

GET/api/v4/webhooks/<id>

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

EstadoCuándo ocurreMensaje
404El webhook no existe en tu cuenta. Llega en details.No such resource for the given Id: <id>
404El id no es un GUID. Llega en details.Not Found

Request

GET
/api/v4/webhooks/<id>
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
}

PUT/api/v4/webhooks/<id>

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:

EstadoCuándo ocurreMensaje
400El id del cuerpo no es el de la ruta. Llega como falla de validación en id.IDs mismatches
400El 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.
404El webhook no existe en tu cuenta. Llega en details.No such resource for the given Id: <id>
404El id no es un GUID. Llega en details.Not Found
409Otro 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

PUT
/api/v4/webhooks/<id>
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
}

DELETE/api/v4/webhooks/<id>

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

EstadoCuándo ocurreMensaje
404El webhook no existe en tu cuenta. Llega en details.No such resource for the given Id: <id>
404El id no es un GUID. Llega en details.Not Found
409Otro 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

DELETE
/api/v4/webhooks/<id>
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
}

POST/api/v4/webhooks/<id>/test

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

EstadoCuándo ocurreMensaje
400El webhook está deshabilitado. Llega en details.El webhook está deshabilitado: habilítalo antes de probarlo.
400El 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.
404El webhook no existe en tu cuenta. Llega en details.No such resource for the given Id: <id>
404El id no es un GUID. Llega en details.Not Found
429Excediste 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

POST
/api/v4/webhooks/<id>/test
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
}

POST/api/v4/webhooks/<id>/rotate-secret

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

EstadoCuándo ocurreMensaje
404El webhook no existe en tu cuenta. Llega en details.No such resource for the given Id: <id>
404El id no es un GUID. Llega en details.Not Found
409Otro 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

POST
/api/v4/webhooks/<id>/rotate-secret
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
}

GET/api/v4/webhooks/<id>/deliveries

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 null si 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; null si 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 status es "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

EstadoCuándo ocurreMensaje
400pageSize está fuera de 1 a 50. Llega como falla de validación en pageSize.'pageSize' debe estar entre 1 y 50.
400pageNumber está fuera de rango. Llega como falla de validación en pageNumber.'pageNumber' debe estar entre 1 y 42949672.
404El webhook no existe en tu cuenta, ni dado de baja. Llega en details.No such resource for the given Id: <id>
404El id no es un GUID. Llega en details.Not Found

Request

GET
/api/v4/webhooks/<id>/deliveries
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
}

¿Le resultó útil esta página?