Webhooks
Un webhook es un aviso que foliios envía a una dirección de tu sistema cuando le pasa algo a un documento. Evita consultar en bucle: te enteras de que un documento quedó aceptado, rechazado o en contingencia, o de que su entrega al comprador terminó.
Antes de empezar: necesitas una dirección pública con HTTPS. Si tu sistema es una planilla o un script que corre en un computador, no puede recibir webhooks: consulta el estado de cada documento hasta que sea final. Es una opción válida.
Configurar el endpoint
Sección titulada «Configurar el endpoint»El endpoint se registra en el portal, por empresa y por ambiente, con su dirección. El endpoint tiene un secreto con el que foliios firma cada aviso, que gestionas en el portal; guárdalo como una clave (ver verificar la firma). Reglas de la dirección:
- Solo
https://, con un nombre o dirección pública de Internet. Direcciones privadas, de loopback, de enlace local o reservadas, y nombres locales (localhost, de una sola etiqueta,*.local,*.internal…), se rechazan al guardar con422 private_url. - Solo los puertos
443y8443; otro,422 port_not_allowed. - foliios no sigue redirecciones.
Qué recibes
Sección titulada «Qué recibes»Un POST con cuerpo JSON y estas cabeceras:
| Cabecera | Contenido |
|---|---|
X-Webhook-Id |
Identificador del evento (el mismo que id del cuerpo). |
X-Webhook-Event |
El tipo, por ejemplo document.accepted. |
X-Webhook-Attempt |
Número de intento: 0 en el primero. |
Foliios-Environment |
sandbox, test o live. |
X-Webhook-Signature |
t=<unix>,v1=<hex>: la firma HMAC-SHA256. Ver verificar la firma. |
El cuerpo trae id, type, created_at, environment, livemode (true solo en live) y data con el documento, su número, CUFE, mensajes de la DIAN, motivo y plazo. Los ocho eventos y su payload de ejemplo están en eventos de webhook.
environment y livemode permiten que un receptor que comparte dirección entre ambientes distinga un evento simulado de uno real sin mirar la empresa.
Cómo responder
Sección titulada «Cómo responder»- Responde
2xxrápido (foliios espera 10 segundos). Procesa después, en segundo plano. - Cualquier otra respuesta, un tiempo agotado o un error de conexión cuenta como fallo. Una redirección (
3xx) no es una entrega. - No hace falta devolver cuerpo.
Reintentos
Sección titulada «Reintentos»Un fallo se reintenta a 1 min, 5 min, 30 min, 2 h, 6 h y 12 h (siete intentos en unas 21 horas). Cada intento envía los mismos bytes y los firma de nuevo con su propia hora. Agotados los intentos, la entrega queda exhausted en el registro del portal, con su cuerpo, y se puede reenviar desde ahí. El registro de entregas se conserva 90 días.
foliios también vigila la salud del endpoint: tres intentos fallidos seguidos lo dejan degradado, seis horas sin un éxito lo dejan caído, y tres éxitos seguidos lo recuperan. Cada cambio llega como aviso a tu cuenta.
Deduplicar y ordenar
Sección titulada «Deduplicar y ordenar»La entrega es al menos una vez: puedes recibir el mismo evento más de una vez.
- Deduplica con
id(oX-Webhook-Id), que se conserva en todos los reintentos. - Ordena con
data.document_version: si los eventos de un mismo documento llegan desordenados, el de versión mayor es el más nuevo. - Haz que tu manejo sea idempotente: aplicar dos veces el mismo evento debe dejar el mismo resultado.
Un receptor mínimo
Sección titulada «Un receptor mínimo»En Python (solo biblioteca estándar). Verifica la firma con la función de verificar la firma:
import jsonimport osfrom http.server import BaseHTTPRequestHandler, HTTPServer
from verificar_firma import verificar # ver la guía de verificación
SECRETO = os.environ["FOLIIOS_WEBHOOK_SECRET"]vistos = set() # en producción, una tabla con el id del evento
class Receptor(BaseHTTPRequestHandler): def do_POST(self): cuerpo = self.rfile.read(int(self.headers.get("Content-Length", 0))) if not verificar(SECRETO, self.headers.get("X-Webhook-Signature", ""), cuerpo): self.send_response(400) self.end_headers() return evento = json.loads(cuerpo) if evento["id"] not in vistos: vistos.add(evento["id"]) print(evento["type"], evento["data"]["document_id"], evento["data"]["number"]) self.send_response(200) self.end_headers()
HTTPServer(("0.0.0.0", 8443), Receptor).serve_forever() # detrás de tu HTTPSProbar en el sandbox
Sección titulada «Probar en el sandbox»Dispara un evento de prueba, sin emitir nada, con POST /v1/sandbox/events (equivale a stripe trigger):
curl -s -X POST https://api.foliios.com/v1/sandbox/events \ -H "Authorization: Bearer $FOLIIOS_API_KEY" -H "Content-Type: application/json" \ -d '{"company_id": "'"$FOLIIOS_COMPANY_ID"'", "event": "document.accepted"}'Responde 202; un tipo de evento desconocido, 422. Llega a la dirección configurada para el ambiente sandbox de la empresa. El evento tiene la misma forma que el real, sobre un documento de prueba que no existe (doc_test_...). Cuenta contra el tope de eventos de prueba por minuto de la empresa (ver los límites en escenarios). Para ver un evento de un documento real, emítelo con un escenario.
Los webhooks también cuentan para la verificación de integración: webhook_delivered exige al menos una entrega 2xx si la empresa tiene endpoint, y es not_applicable si no lo tiene.