Skip to content

Webhooks ​

Registra URLs de callback HTTPS para recibir notificaciones de eventos casi en tiempo real. Cuando la API crea o actualiza una notificación (por ejemplo, un comprobante es autorizado, un certificado está por expirar), envía inmediatamente un POST con un payload firmado a cada endpoint activo que esté suscrito a ese tipo de evento.

POST   /v1/webhooks
GET    /v1/webhooks
PATCH  /v1/webhooks/:id
DELETE /v1/webhooks/:id

Autenticación ​

Authorization: Bearer <api-key> — cualquier llave activa del tenant.

Límites por plan ​

PlanMáximo de endpoints activos
FREE1
STARTER2
GROWTH5
BUSINESS10

Objeto de endpoint de webhook ​

json
{
  "id": "00000000-0000-0000-0000-000000000001",
  "url":        "https://app.example.com/v1/comprobify/events",
  "eventTypes": ["DOCUMENT_AUTHORIZED", "CERT_EXPIRING"],
  "active":     true,
  "createdAt":  "2026-05-31T10:00:00.000Z",
  "updatedAt":  "2026-05-31T10:00:00.000Z"
}
CampoTipoDescripción
idstring (UUID)Identificador estable
urlstringURL HTTPS a la que la API envía los eventos
eventTypesstring[]Tipos de evento suscritos. Un arreglo vacío significa suscripción a todos los tipos de evento.
activebooleanfalse después de darse de baja; los envíos históricos se conservan
createdAtstringTimestamp ISO del registro
updatedAtstringTimestamp ISO de la última actualización

Nota: el secret de firma nunca se devuelve después del registro inicial. Guárdalo inmediatamente al crearlo.


Registrar un endpoint ​

POST /v1/webhooks

Crea un nuevo endpoint de webhook y devuelve el secreto de firma. El secreto se muestra exactamente una vez — guárdalo de inmediato.

Cuerpo de la solicitud ​

json
{
  "url":        "https://app.example.com/v1/comprobify/events",
  "eventTypes": ["DOCUMENT_AUTHORIZED"]
}
CampoTipoRequeridoDescripción
urlstringSíDebe ser una URL HTTPS válida
eventTypesstring[]NoTipos de evento a los que suscribirse. Omite o pasa [] para recibir todos los eventos. Valores válidos: DOCUMENT_AUTHORIZED, CERT_EXPIRING, CERT_EXPIRED, SRI_SUBMISSION_FAILED, EMAIL_DELIVERY_FAILED, QUOTA_WARNING

Respuesta ​

201 Created

json
{
  "ok": true,
  "endpoint": {
    "id": "00000000-0000-0000-0000-000000000001",
    "url":        "https://app.example.com/v1/comprobify/events",
    "eventTypes": ["DOCUMENT_AUTHORIZED"],
    "active":     true,
    "createdAt":  "2026-05-31T10:00:00.000Z",
    "updatedAt":  "2026-05-31T10:00:00.000Z"
  },
  "secret": "a3f5c8d1e2b4..."
}

Guarda el secret de forma segura. Se usa para verificar el header X-Comprobify-Signature en las solicitudes de webhook entrantes.

Errores ​

Estado HTTPCódigoCuándo ocurre
400VALIDATION_FAILEDurl no es una URL HTTPS válida, o un eventType no es reconocido
401UNAUTHORIZEDAPI key faltante o inválida
402WEBHOOK_ENDPOINT_LIMIT_REACHEDSe alcanzó el límite del plan en endpoints activos
403EMAIL_VERIFICATION_REQUIREDEl correo de la cuenta no está verificado (p. ej. tras recuperar la cuenta) — registrar un endpoint o cambiar su URL/tipos de evento lo requiere; desactivarlo no

Listar endpoints ​

GET /v1/webhooks

Devuelve todos los endpoints activos del tenant (los secretos de firma nunca se incluyen).

Respuesta ​

200 OK

json
{
  "ok": true,
  "endpoints": [ ... ],
  "limit": { "max": 2, "used": 1 }
}

limit.max es cuántos endpoints activos puedes tener en total antes de que POST /v1/webhooks devuelva 402 WEBHOOK_ENDPOINT_LIMIT_REACHED, y limit.used es tu conteo actual — coincide exactamente con el máximo de tu plan (ver Planes de suscripción). En Free/Solo/Lite max es 0: esos planes no venden webhooks propios por self-service, y el endpoint interno que la aplicación web usa para sí misma nunca cuenta aquí ni aparece en este listado.


Actualizar un endpoint ​

PATCH /v1/webhooks/:id

Actualiza la URL, las suscripciones de eventos, o el indicador active de un endpoint existente. Todos los campos son opcionales — envía solo lo que quieras cambiar.

Cuerpo de la solicitud ​

json
{
  "url":        "https://app.example.com/v1/comprobify/events-v2",
  "eventTypes": ["DOCUMENT_AUTHORIZED", "CERT_EXPIRED"],
  "active":     true
}

Respuesta ​

200 OK

json
{
  "ok": true,
  "endpoint": { ... }
}

Errores ​

Estado HTTPCódigoCuándo ocurre
400VALIDATION_FAILEDurl inválida, eventType desconocido, o active no booleano
401UNAUTHORIZEDAPI key faltante o inválida
404NOT_FOUNDEndpoint no encontrado o pertenece a otro tenant

Dar de baja un endpoint ​

DELETE /v1/webhooks/:id

Elimina el endpoint de forma lógica (active = false). El endpoint deja de recibir envíos inmediatamente. Los registros de envíos pasados se conservan en webhook_deliveries con fines de auditoría.

Respuesta ​

200 OK

json
{ "ok": true }

Errores ​

Estado HTTPCódigoCuándo ocurre
401UNAUTHORIZEDAPI key faltante o inválida
404NOT_FOUNDEndpoint no encontrado o pertenece a otro tenant

Recepción de webhooks ​

Formato del payload ​

json
{
  "event":      "DOCUMENT_AUTHORIZED",
  "deliveryId": "00000000-0000-0000-0000-000000000017",
  "timestamp":  1748649600,
  "tenantId": "00000000-0000-0000-0000-000000000007",
  "data": {
    "id": "00000000-0000-0000-0000-000000000042",
    "type":      "DOCUMENT_AUTHORIZED",
    "severity":  "INFO",
    "title":     "Factura autorizada",
    "message":   "La factura 001-001-000000012 de ACME Corp fue autorizada por el SRI.",
    "metadata":  { ... },
    "issuerId": "00000000-0000-0000-0000-000000000003",
    "readAt":    null,
    "expiresAt": null,
    "createdAt": "2026-05-28T14:30:00.000Z"
  }
}
CampoDescripción
eventEl tipo de notificación (refleja data.type)
deliveryIdID de la fila en webhook_deliveries. Úsalo para deduplicación — los reintentos del mismo envío tienen el mismo deliveryId.
timestampTimestamp Unix (segundos) de cuándo se creó originalmente el evento
tenantIdTu ID de tenant
dataObjeto de notificación completo

Verificación de firmas ​

Cada solicitud incluye:

X-Comprobify-Signature: sha256=<hex>
X-Comprobify-Timestamp: <unix seconds>

Para verificar:

  1. Lee el cuerpo crudo de la solicitud como una cadena (antes de analizarlo como JSON).
  2. Calcula HMAC-SHA256(secret, "${timestamp}.${rawBody}") donde secret es el secreto de firma de tu endpoint.
  3. Compara el resultado con la porción sha256= de X-Comprobify-Signature usando una función de comparación de tiempo constante.
  4. Rechaza la solicitud si las firmas no coinciden o si X-Comprobify-Timestamp tiene más de 5 minutos de antigüedad.

Ejemplo en Node.js:

js
const crypto = require('crypto');

function verifyWebhook(secret, req) {
  const timestamp = req.headers['x-comprobify-timestamp'];
  const signature = req.headers['x-comprobify-signature'];
  const rawBody   = req.rawBody; // Buffer or string before JSON.parse

  if (!timestamp || !signature) return false;

  // Reject requests older than 5 minutes
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (age > 300) return false;

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

Requisitos de la respuesta ​

Devuelve cualquier estado 2xx para confirmar la recepción. Cualquier otro estado (incluyendo 3xx) se trata como un fallo y dispara un reintento.

Procesa el evento de forma asíncrona — responde con 200 de inmediato y maneja el payload en un job en segundo plano para evitar timeouts.

Deduplicación ​

Usa deliveryId para deduplicar. Un reintento del mismo envío tiene el mismo deliveryId pero llega en una nueva solicitud HTTP. Tu manejador debe ser idempotente: procesar el mismo deliveryId dos veces debe producir el mismo resultado.

Calendario de reintentos ​

IntentoMomento
1Inmediatamente al crearse el evento
230 segundos después de que falla el intento 1
32 minutos después de que falla el intento 2
FAILEDDespués de 3 intentos fallidos — no hay más reintentos

Si se agotan todos los reintentos, usa GET /v1/notifications?sinceId=<lastId> para ponerte al día con los eventos perdidos.

Documentación de la API de Comprobify — API v1.3.1