> ## Documentation Index
> Fetch the complete documentation index at: https://docs.querying.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Charge utile de livraison, calendrier des nouvelles tentatives et comment écrire un handler sûr.

Fournissez `webhook.url` sur une tâche et nous y enverrons le résultat en POST quand la tâche atteint un état final :
`COMPLETED` ou `FAILED`.

```json theme={null}
{
  "taskType": "PERPLEXITY",
  "payload": { "prompt": "best wireless earbuds 2026" },
  "webhook": { "url": "https://your-server.example.com/hook" }
}
```

## Charge utile de livraison

Nous envoyons en POST `Content-Type: application/json` avec ce corps :

<ResponseField name="task" type="object" required>
  Les mêmes métadonnées de tâche qu'à la soumission, désormais avec un `status` final et votre `idempotencyKey` renvoyée.
</ResponseField>

<ResponseField name="credits" type="object" required>
  Le coût en crédits du moteur : `2` pour Perplexity, `3` pour ChatGPT, etc. (1 à 3 crédits selon le moteur).
  `creditsCharged` vaut `0` si la tâche a échoué. Contrairement aux réponses de soumission et de polling, qui indiquent
  toujours zéro, ce champ n'est pas nul.
</ResponseField>

<ResponseField name="response" type="object" required>
  Le résultat propre au moteur. Consultez [Moteurs](/fr/engines/overview). Pour une tâche échouée, c'est
  `{ "error": "<reason>" }` au lieu du résultat du moteur.
</ResponseField>

```json theme={null}
{
  "task": {
    "id": "8f2c1e40-...",
    "taskType": "PERPLEXITY",
    "status": "COMPLETED",
    "priority": 1,
    "createdAt": "2026-07-09T04:12:00.000Z",
    "idempotencyKey": "job-42:prompt-7:perplexity"
  },
  "credits": { "creditsToCharge": 3, "creditsCharged": 3 },
  "response": {
    "text": "The best wireless earbuds in 2026 are ...",
    "sources": [{ "position": 1, "url": "https://...", "label": "..." }]
  }
}
```

Une tâche échouée est aussi livrée :

```json theme={null}
{
  "task": { "id": "8f2c1e40-...", "status": "FAILED", "...": "..." },
  "credits": { "creditsToCharge": 3, "creditsCharged": 0 },
  "response": { "error": "ENGINE_TIMEOUT: The engine did not answer before the task deadline." }
}
```

## Nouvelles tentatives

Renvoyez n'importe quel `2xx` pour accuser réception. Tout le reste, timeout compris, est un échec et nous
réessayons avec un backoff exponentiel :

| Tentative | Déclenchement |
| - | - |
| 1 | immédiatement |
| 2 | après 2 minutes |
| 3 | après 4 minutes |
| 4 | après 8 minutes |
| 5 | après 16 minutes |

Après la cinquième tentative, la livraison est abandonnée et journalisée. Il n'existe pas de file de lettres mortes
consultable. Pour récupérer le résultat via l'API de tâches, interrogez-la pendant la fenêtre publique de 24 heures.
Cette fenêtre ne définit pas la durée de stockage de l'enregistrement sous-jacent.

Chaque tentative a un timeout de requête de **30 secondes**.

<Warning>
  Du point de vue de la tâche, la livraison est de type « envoyer et oublier ». Une tâche terminée dont le webhook ne
  peut jamais être livré reste `COMPLETED` sur `GET /v1/async/task/{id}` : le statut décrit le travail du moteur, pas la notification.
</Warning>

## Écrire un handler sûr

<Steps>
  <Step title="Accuser réception vite">
    Renvoyez `200` dès que vous avez mis la charge utile en file de façon durable. Faites l'analyse et les écritures en
    base ensuite. Un handler lent consomme le timeout de 30 secondes et provoque une nouvelle tentative indésirable.
  </Step>

  <Step title="Dédupliquer sur idempotencyKey">
    À cause des nouvelles tentatives, votre handler peut légitimement recevoir la même tâche plusieurs fois. Rapprochez
    sur `task.idempotencyKey` (ou `task.id`) et rendez l'écriture idempotente.
  </Step>

  <Step title="Brancher sur le statut, pas sur la présence">
    Vérifiez explicitement `task.status === "FAILED"`. Une tâche échouée livre aussi un webhook.
  </Step>
</Steps>

<Note>
  Les requêtes webhook ne portent ni signature ni secret partagé. Si votre endpoint est public, considérez la charge
  utile comme non fiable et utilisez un chemin d'URL impossible à deviner, ou placez le handler derrière des
  restrictions au niveau réseau.
</Note>
