Timbre

Webhooks

Cómo Timbre te avisa, y cómo verificar que fue Timbre.

Cuando Hacienda resuelve, Timbre llama a tu servidor. Es la forma correcta de enterarte: no dependés de consultar y no perdés el veredicto si tu proceso estaba caído —la entrega se reintenta.

Todo lo de esta página —registrar un endpoint, probarlo, rotar el secreto, darlo de baja, ver las entregas— también se hace desde el panel, en Integración → Webhooks, sin escribir una sola petición. Lo de abajo es para cuando lo vas a manejar por código.

Registrar un endpoint

Petición
curl -X POST https://api.timbre.cr/internal/v1/webhooks/endpoints \
  -H "Authorization: Bearer timbre_sk_…" \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://tu-servidor.cr/timbre"
  }'

La URL debe ser https:. Un aviso lleva la clave y la identificación del contribuyente, y eso no viaja en claro.

La respuesta trae el secreto, una sola vez. Guardalo al recibirlo.

Verificar la firma

Cada entrega llega con tres encabezados:

Timbre-Delivery-Id: …
Timbre-Event-Type: invoice.accepted
Timbre-Signature: t=1712345678,v1=…

La firma es HMAC-SHA256 sobre esta cadena exacta:

v1:{deliveryId}:{t}:{cuerpo crudo}

Tres cosas importan al verificarla. Usá el cuerpo crudo, tal cual llegó —si lo parseás y lo volvés a serializar, la firma no va a coincidir. Compará en tiempo constante, no con ==. Y rechazá lo que tenga t viejo; 300 segundos de tolerancia es razonable.

Sin lo último, cualquiera que haya capturado una entrega válida puede reproducirla mañana.

Timbre-Signature puede traer más de un v1t=…,v1=abc…,v1=def…— mientras estés en la ventana de solapamiento de una rotación. Aceptá la entrega si cualquiera de los v1 coincide con tu secreto; no asumas que es uno solo ni que el primero es el vigente.

Los eventos

EventoCuándo
invoice.finalizedNumerado y encaminado
invoice.processingHacienda lo recibió
invoice.acceptedAceptado
invoice.rejectedRechazado
invoice.permanent_errorFalló definitivamente
webhook.testUna prueba que pediste vos

Probar sin emitir

Petición
curl -X POST https://api.timbre.cr/internal/v1/webhooks/endpoints/{id}/test \
  -H "Authorization: Bearer timbre_sk_…"

Manda un webhook.test real, firmado igual que los demás, para que verifiques tu verificación antes de que haya un documento de por medio.

Rotar el secreto

Petición
curl -X POST https://api.timbre.cr/internal/v1/webhooks/endpoints/{id}/secrets \
  -H "Authorization: Bearer timbre_sk_…"

El anterior sigue siendo válido durante un solapamiento, para que puedas desplegar el nuevo sin perder entregas en el medio. Cuánto dura lo elegís vos con ?overlapSeconds=; sin ese parámetro usa el valor por defecto del servidor, y 0 es corte inmediato —el secreto anterior deja de firmar ya.

Administrar tus endpoints

Petición
curl https://api.timbre.cr/internal/v1/webhooks/endpoints \
  -H "Authorization: Bearer timbre_sk_…"

Lista los endpoints del proyecto —nunca con el secreto, que solo se ve al crearlos o al rotarlos. Para dar de baja uno:

Petición
curl -X DELETE https://api.timbre.cr/internal/v1/webhooks/endpoints/{id} \
  -H "Authorization: Bearer timbre_sk_…"

Deja de recibir eventos nuevos. Las entregas que ya existían quedan como evidencia y no se reintentan mientras esté deshabilitado.

Ver qué se entregó

Petición
curl https://api.timbre.cr/internal/v1/webhooks/deliveries \
  -H "Authorization: Bearer timbre_sk_…"

Cada entrega queda con su estado —pending, delivered o failed— y la evidencia de cada intento.