Guides

Webhooks

Recevoir les notifications de fin de tâche batch

Pour les tâches batch qui durent plus d'une minute, les Webhook sont moins coûteux et plus rapides que le polling. Thunderbit fait un POST vers ton URL quand une tâche atteint un état terminal.

Configuration à la soumission

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

Le champ webhook.headers accepte une map d'en-têtes personnalisés, mais il est réservé pour un usage futur et n'est pas encore envoyé par le serveur. Base ta vérification uniquement sur les en-têtes documentés ci-dessous.

En-têtes de requête

Chaque livraison inclut les 4 en-têtes suivants :

HeaderValue
Content-Typeapplication/json
X-Webhook-Eventbatch.completed
X-Webhook-TimestampUnix epoch en millisecondes
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 vaut batch_distill ou batch_extract.
  • status vaut COMPLETED, FAILED ou CANCELLED.

Le payload est volontairement minimal — récupère les résultats complets via GET /batch/distill/{id} (ou /batch/extract/{id}) après réception du callback.

Vérification de la signature

Lorsque secret est défini, chaque livraison inclut un en-tête X-Webhook-Signature.

  • Algorithme : HMAC-SHA256
  • Chaîne à signer : <X-Webhook-Timestamp>.<raw-json-body> (un . littéral entre les deux)
  • Format de sortie : sha256=<base64-encoded-hash> (Base64, pas hex)

Vérifie sur les octets bruts de la requête (ne re-sérialise pas le JSON), puis compare en temps constant.

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)

Protection contre le rejeu

Rejette la requête si |now - X-Webhook-Timestamp| > 60000 (fenêtre de 60 s). Le timestamp est en millisecondes depuis l'époque Unix. Ne fais jamais confiance à un webhook non signé en production.

Comportement de retry

  • Timeout par livraison : 30 secondes
  • Nombre max de retries : 3
  • Backoff : exponentiel, démarrant à 1 seconde, plafonné à 10 secondes
  • Fenêtre totale de bout en bout : ~120 secondes

Une fois tous les retries épuisés, la tâche reste complète de notre côté — ton endpoint doit être idempotent (utilise id comme clé de déduplication).