Guide

Webhooks

Ricevi notifiche di completamento dei batch job

Per i batch job che durano più di un minuto, i webhook sono più economici e veloci del polling. Thunderbit fa POST al tuo URL quando un job raggiunge uno stato terminale.

Configurazione all'invio

{
  "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" }
  }
}

Il campo webhook.headers accetta una map di header personalizzati, ma è riservato per uso futuro e non viene ancora inviato dal server. Basa la verifica esclusivamente sugli header documentati qui sotto.

Header della richiesta

Ogni consegna include i seguenti 4 header:

HeaderValue
Content-Typeapplication/json
X-Webhook-Eventbatch.completed
X-Webhook-TimestampUnix epoch in millisecondi
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 vale batch_distill o batch_extract.
  • status è uno tra COMPLETED, FAILED o CANCELLED.

Il payload è volutamente compatto — recupera i risultati completi con GET /batch/distill/{id} (oppure /batch/extract/{id}) dopo aver ricevuto la callback.

Verifica della firma

Quando secret è impostato, ogni consegna include un header X-Webhook-Signature.

  • Algoritmo: HMAC-SHA256
  • Stringa da firmare: <X-Webhook-Timestamp>.<raw-json-body> (un . letterale tra i due)
  • Formato di output: sha256=<base64-encoded-hash> (Base64, non hex)

Verifica sui byte grezzi della richiesta (non riserializzare il JSON) e poi confronta a tempo costante.

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)

Protezione contro replay

Rifiuta la richiesta se |now - X-Webhook-Timestamp| > 60000 (finestra di 60 s). Il timestamp è in millisecondi dall'epoch Unix. Non fidarti mai di un webhook non firmato in produzione.

Comportamento dei retry

  • Timeout per consegna: 30 secondi
  • Retry massimi: 3
  • Backoff: esponenziale, partenza 1 secondo, tetto 10 secondi
  • Finestra totale end-to-end: ~120 secondi

Esauriti i retry, il job sul nostro lato risulta comunque completato — il tuo endpoint deve essere idempotente (usa id come chiave di deduplica).