Saltar al contenido
Menú
Saltar la navegación de la documentación

Español API 2026-10-22

Documentación · Webhooks · API v1 · versión 2026-10-22

Webhooks

Avisos firmados (Standard Webhooks) cuando un documento se valida, se rechaza, queda en error o se entrega; cómo crearlos, verificarlos, reintentos y rotación del secreto.

Ver como Markdown

Un webhook es una dirección de tu servidor a la que Trevol le avisa cuando un documento se valida, se rechaza, queda en error o se entrega. Evita que tengas que preguntar, y es la forma de enterarte del resultado de un 202 processing.

  • Cada webhook es de una sola empresa y un solo ambiente, como las llaves. Hasta 5 por empresa y ambiente.
  • Créalo en la consola o por API (llave de escritura). El secreto (whsec_…) se muestra una sola vez: guárdalo en tu servidor.
  • La URL debe ser https:// con un nombre de dominio (no una IP), sin usuario ni clave y con puerto 443 o 1024–65535. Trevol resuelve el nombre en cada envío y nunca manda a direcciones privadas o locales.
  • Prueba el endpoint con POST /v1/webhook_endpoints/{id}/test: llega un evento webhook.test.

Crear un endpoint

POST /v1/webhook_endpoints

cURL
curl -X POST "https://api.trevol.si/v1/webhook_endpoints" \
  -H "Authorization: Bearer $TREVOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://tu-servidor.co/webhooks/trevol",
  "description": "Producción: facturación",
  "events": [
    "invoice.accepted",
    "invoice.rejected",
    "credit_note.accepted"
  ]
}'

Se administra en Webhooks

La respuesta trae secret (whsec_…) solo en esta llamada y al rotarlo.

POST /v1/webhook_endpoints/{id}/test

cURL
curl -X POST "https://api.trevol.si/v1/webhook_endpoints/we_3f0c1a2b3f0c1a2b3f0c1a2b3f0c1a2b/test" \
  -H "Authorization: Bearer $TREVOL_API_KEY"

Probar en el Playground

Los eventos

Hay 50 tipos (con webhook.test): los de la factura y las notas, el documento soporte, el documento equivalente (con sus notas de ajuste), la nómina y los eventos RADIAN, y los de la cuenta. Están todos en Tipos de evento. Una lista explícita admite hasta 60. Si suscribes ["*"] recibes todos los que existan.

Webhooks creados antes

Un webhook creado antes de que existieran los de documento soporte, equivalente, nómina y RADIAN con ["*"] sigue recibiendo solo los 22 de entonces (su catalog_version es 1) hasta que guardes de nuevo su lista de eventos —con PATCH y events, o desde la consola—; los nuevos nacen con catalog_version: 2. Así ningún receptor existente recibe tipos que no conoce. balance.low, numbering.low y numbering.expiring solo existen en producción (llaves tvl_live_…).

Qué llega

El evento lleva un resumen del documento (identificador, estado, número, CUFE o CUDE, total, códigos de la DIAN y entrega), nunca los datos del comprador ni las líneas, y en ….error un texto fijo por código. Pide el documento completo con GET /v1/invoices/{id}. El webhook puede llegar antes que la respuesta HTTP del POST: usa data.id, que es el mismo.

HTTP
POST /trevol HTTP/1.1
Content-Type: application/json
User-Agent: Trevol-Webhooks/1.0 (+https://platform.trevol.si/docs/webhooks)
webhook-id: evt_8c1e0f0a7d3b4a6f9e2d1c0b5a4f3e2d
webhook-timestamp: 1761163445
webhook-signature: v1,K5oZfzN0ZGxPpoGxC2c0vL1o0U3q6Qh2oA0h1c8l8bE=

{"id":"evt_8c1e0f0a7d3b4a6f9e2d1c0b5a4f3e2d","object":"event","type":"invoice.accepted","timestamp":"2026-10-22T20:04:05Z",
 "environment":"test","api_version":"2026-10-22",
 "data":{"object":"invoice","id":"inv_08cd6774e31e401c9fa0fbf92b3fbdc0","status":"accepted","environment":"test",
         "number":"SETP994307051","prefix":"SETP","external_id":"VENTA-2026-000123","origin":"api","cufe":"52cede538652…",
         "issued_at":"2026-10-22T15:04:05-05:00","total":"178500.00","invoice":null,
         "dian":{"status_code":"00","rules":[]},"delivery":{"pdf":false,"zip":false,"email":{"status":"pending"}}}}

Verifica la firma

Seguimos Standard Webhooks. Cada entrega trae:

  • webhook-id: el mismo en cada reintento y en un reenvío manual. Úsalo para no procesar dos veces el mismo evento: la verificación no evita repeticiones dentro de los 5 minutos ni los reintentos de entrega.
  • webhook-timestamp: segundos Unix del intento.
  • webhook-signature: v1, y el HMAC-SHA256 en base64 de webhook-id.webhook-timestamp.cuerpo. La clave son los 32 bytes que siguen a whsec_ (en base64). Durante la gracia de una rotación llegan dos firmas separadas por un espacio: basta con que una coincida.

Calcúlala sobre el cuerpo tal como llegó (los bytes exactos, nunca el JSON ya decodificado), compárala en tiempo constante y rechaza los avisos con más de 5 minutos de diferencia.

Hasta que los SDK se publiquen, verifica con este código (Node.js, sin dependencias) o con una biblioteca abierta de Standard Webhooks de tu lenguaje:

JavaScript
import { createHmac, timingSafeEqual } from 'node:crypto';

// cuerpo: el texto crudo; cabeceras: un objeto con los nombres en minúsculas (Express: req.headers).
export function verificar(cuerpo, cabeceras, secreto) {
	const id = cabeceras['webhook-id'];
	const marca = cabeceras['webhook-timestamp'];
	const firmas = cabeceras['webhook-signature'];
	if (!id || !/^\d+$/.test(marca ?? '') || !firmas) return false;
	if (Math.abs(Date.now() / 1000 - Number(marca)) > 300) return false; // más de 5 minutos
	const clave = Buffer.from(secreto.replace(/^whsec_/, ''), 'base64');
	const esperada = createHmac('sha256', clave).update(`${id}.${marca}.${cuerpo}`).digest();
	return firmas.split(' ').some((f) => {
		const [version, valor] = f.split(',');
		if (version !== 'v1' || !valor) return false;
		const recibida = Buffer.from(valor, 'base64');
		return recibida.length === esperada.length && timingSafeEqual(recibida, esperada);
	});
}

Deduplica por webhook-id: esta verificación no evita repeticiones dentro de los 5 minutos ni los reintentos de entrega. Nunca compares las firmas con ==: usa una comparación en tiempo constante.

Entrega y reintentos

  • Cualquier 2xx en 10 segundos cuenta como entregado. Un 3xx (no seguimos redirecciones), un 4xx, un 5xx, un tiempo agotado o un error de TLS cuentan como fallo. No guardamos la respuesta de tu servidor.
  • Reintentos con espera creciente: 1 min, 5 min, 30 min, 2 h, 5 h, 10 h y 14 h (8 intentos en unas 32 horas). Después la entrega queda failed; reenvíala con POST /v1/events/{id}/resend (3 intentos).
  • Si el endpoint falla sin parar durante 5 días, Trevol lo desactiva (status: disabled) y le avisa por correo al titular. Se reactiva con PATCH {"status": "enabled"}.
  • GET /v1/webhook_endpoints/{id}/deliveries muestra cada entrega y su error (timeout, http_5xx, blocked_address…). GET /v1/events guarda los eventos 30 días: sirve para recuperar lo que se perdió mientras tu servidor estaba caído.

GET /v1/webhook_endpoints/{id}/deliveries

cURL
curl "https://api.trevol.si/v1/webhook_endpoints/we_3f0c1a2b3f0c1a2b3f0c1a2b3f0c1a2b/deliveries?limit=100" \
  -H "Authorization: Bearer $TREVOL_API_KEY"

Probar en el Playground

POST /v1/events/{id}/resend

cURL
curl -X POST "https://api.trevol.si/v1/events/evt_3f0c1a2b3f0c1a2b3f0c1a2b3f0c1a2b/resend" \
  -H "Authorization: Bearer $TREVOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "endpoint_id": "we_3f0c1a2b3f0c1a2b3f0c1a2b3f0c1a2b"
}'

Probar en el Playground

Rotar el secreto

Si pierdes el secreto, rótalo: el anterior sigue firmando hasta 72 horas para que no pierdas avisos (durante ese tiempo llegan dos firmas en webhook-signature).

POST /v1/webhook_endpoints/{id}/rotate_secret

cURL
curl -X POST "https://api.trevol.si/v1/webhook_endpoints/we_3f0c1a2b3f0c1a2b3f0c1a2b3f0c1a2b/rotate_secret" \
  -H "Authorization: Bearer $TREVOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "grace_period_hours": 24
}'

Se administra en Webhooks