Gidsen

Webhooks

Ontvang voltooiingsmeldingen voor batchjobs

Voor batchjobs die langer dan een minuut duren zijn Webhooks goedkoper en sneller dan polling. Thunderbit POST't naar jouw URL wanneer een job een eindstatus bereikt.

Configureren bij het indienen

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

Het veld webhook.headers accepteert een map met aangepaste headers, maar is gereserveerd voor toekomstig gebruik en wordt momenteel niet door de server verstuurd. Stem je verificatie uitsluitend af op de hieronder gedocumenteerde headers.

Request-headers

Elke aflevering bevat de volgende 4 headers:

HeaderValue
Content-Typeapplication/json
X-Webhook-Eventbatch.completed
X-Webhook-TimestampUnix epoch in milliseconden
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 is batch_distill of batch_extract.
  • status is COMPLETED, FAILED of CANCELLED.

De payload is bewust klein gehouden — haal de volledige resultaten op met GET /batch/distill/{id} (of /batch/extract/{id}) nadat je de callback hebt ontvangen.

Signatuurverificatie

Als secret is ingesteld, bevat elke aflevering een X-Webhook-Signature-header.

  • Algoritme: HMAC-SHA256
  • Te ondertekenen string: <X-Webhook-Timestamp>.<raw-json-body> (een letterlijke . ertussen)
  • Uitvoerformaat: sha256=<base64-encoded-hash> (Base64, geen hex)

Verifieer op de ruwe request-bytes (serialiseer de JSON niet opnieuw) en vergelijk in constante tijd.

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)

Replay-bescherming

Wijs de request af als |now - X-Webhook-Timestamp| > 60000 (venster van 60 s). De timestamp is in milliseconden sinds de Unix epoch. Vertrouw in productie nooit een ongesigneerde webhook.

Retry-gedrag

  • Timeout per aflevering: 30 seconden
  • Maximum aantal retries: 3
  • Backoff: exponentieel, startend op 1 seconde, gemaximeerd op 10 seconden
  • Totaal end-to-end-venster: ~120 seconden

Nadat alle retries zijn mislukt, blijft de job aan onze kant voltooid — jouw endpoint moet idempotent zijn (gebruik id als deduplicatiesleutel).