Guías

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:

HeaderValue
Content-Typeapplication/json
X-Webhook-Eventbatch.completed
X-Webhook-TimestampUnix epoch en milisegundos
X-Webhook-Signaturesha256=<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"
}
  • jobType es batch_distill o batch_extract.
  • status es uno de COMPLETED, FAILED o CANCELLED.

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).