Guias

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:

HeaderValue
Content-Typeapplication/json
X-Webhook-Eventbatch.completed
X-Webhook-TimestampUnix epoch em milissegundos
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 é batch_distill ou batch_extract.
  • status é um de COMPLETED, FAILED ou CANCELLED.

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