Webhooks
Receba notificações de conclusão de jobs em batch
Para jobs em batch que duram mais de um minuto, webhooks são mais baratos e rápidos do que polling. A Thunderbit faz POST para sua URL quando um job atinge um estado terminal.
Configurar no envio
{
"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" }
}
}O campo webhook.headers aceita um mapa de cabeçalhos personalizados, mas está reservado para uso futuro e ainda não é enviado pelo servidor. Baseie sua verificação somente nos cabeçalhos documentados abaixo.
Cabeçalhos da requisição
Cada entrega inclui os seguintes 4 cabeçalhos:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Webhook-Event | batch.completed |
X-Webhook-Timestamp | Unix epoch em milissegundos |
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"
}jobTypeébatch_distilloubatch_extract.statusé um deCOMPLETED,FAILEDouCANCELLED.
O payload é propositalmente pequeno — busque os resultados completos via GET /batch/distill/{id} (ou /batch/extract/{id}) após receber o callback.
Verificação de assinatura
Quando secret é definido, toda entrega inclui um cabeçalho X-Webhook-Signature.
- Algoritmo: HMAC-SHA256
- String a assinar:
<X-Webhook-Timestamp>.<raw-json-body>(um.literal entre os dois) - Formato de saída:
sha256=<base64-encoded-hash>(Base64, não hex)
Verifique sobre os bytes brutos da requisição (não re-serialize o JSON) e compare em tempo 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)Proteção contra replay
Rejeite a requisição se |now - X-Webhook-Timestamp| > 60000 (janela de 60 s). O timestamp está em milissegundos desde a Unix epoch. Nunca confie em um webhook sem assinatura em produção.
Comportamento de retry
- Timeout por entrega: 30 segundos
- Retries máximos: 3
- Backoff: exponencial, começando em 1 segundo, limitado a 10 segundos
- Janela total end-to-end: ~120 segundos
Mesmo após todos os retries falharem, o job permanece concluído do nosso lado — seu endpoint precisa ser idempotente (use id como chave de deduplicação).