Webhooks
Recibe notificaciones cuando un job en lote se completa
Para jobs en lote que duran más de un minuto, los Webhooks son más baratos y rápidos que el polling. Thunderbit hace POST a tu URL cuando un job alcanza un estado terminal.
Configurar al enviar
{
"urls": ["https://example.com/page1"],
"webhook": {
"url": "https://your-server.com/api/webhook/distill",
"secret": "whsec_your_secret_key",
"headers": { "X-Custom-Auth": "your-token" }
}
}El campo webhook.headers acepta un mapa de cabeceras personalizadas, pero está reservado para uso futuro y aún no es enviado por el servidor. Diseña tu verificación únicamente con base en las cabeceras documentadas a continuación.
Cabeceras de la petición
Cada entrega incluye las siguientes 4 cabeceras:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Webhook-Event | batch.completed |
X-Webhook-Timestamp | Unix epoch en milisegundos |
X-Webhook-Signature | sha256=<base64-encoded HMAC-SHA256> |
Payload
{
"id": "batch_a1b2c3d4...",
"jobType": "batch_distill",
"status": "COMPLETED",
"total": 50,
"completed": 49,
"failed": 1,
"creditsUsed": 100,
"createdAt": "2026-04-26T10:00:00Z",
"completedAt": "2026-04-26T10:05:23Z"
}jobTypeesbatch_distillobatch_extract.statuses uno deCOMPLETED,FAILEDoCANCELLED.
El payload se mantiene pequeño a propósito — obtén los resultados completos con GET /batch/distill/{id} (o /batch/extract/{id}) tras recibir el callback.
Verificación de firma
Cuando se configura secret, cada entrega incluye una cabecera X-Webhook-Signature.
- Algoritmo: HMAC-SHA256
- Cadena a firmar:
<X-Webhook-Timestamp>.<raw-json-body>(un.literal entre ambos) - Formato de salida:
sha256=<base64-encoded-hash>(Base64, no hex)
Verifica con los bytes crudos de la petición (no reserialices el JSON) y compara en tiempo constante.
import hmac, hashlib, base64
def verify(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
base = f"{timestamp}.{raw_body.decode('utf-8')}".encode('utf-8')
digest = hmac.new(secret.encode('utf-8'), base, hashlib.sha256).digest()
expected = "sha256=" + base64.b64encode(digest).decode('ascii')
return hmac.compare_digest(expected, signature)Protección contra replay
Rechaza la petición si |now - X-Webhook-Timestamp| > 60000 (ventana de 60 s). El timestamp está en milisegundos desde la época Unix. Nunca confíes en un webhook sin firmar en producción.
Política de reintentos
- Timeout por entrega: 30 segundos
- Reintentos máximos: 3
- Backoff: exponencial, comenzando en 1 segundo, con tope de 10 segundos
- Ventana total extremo a extremo: ~120 segundos
Tras agotar los reintentos, el job sigue completo en nuestro lado — tu endpoint debe ser idempotente (usa id como clave de deduplicación).