---
title: Rappels | RunAPI
description: Recevoir et vérifier de manière sécurisée les livraisons de callbacks
  de Task.
url: https://runapi.ai/fr/docs/guides/task-api/callbacks.md
canonical: https://runapi.ai/fr/docs/guides/task-api/callbacks
locale: fr
---

> Version HTML: https://runapi.ai/fr/docs/guides/task-api/callbacks
> Index du site pour les agents: https://runapi.ai/llms.txt

# Rappels

Utilisez les callbacks pour recevoir les événements du cycle de vie des Tasks à un point de terminaison HTTPS public. Vérifiez chaque callback avant de traiter son corps JSON afin que
seules les livraisons signées pour votre compte puissent modifier l'état de l'application.

## Configurer une URL de callback

Ajoutez un `callback_url` HTTPS public lorsque vous créez une tâche. Le corps de l'événement
et les états du cycle de vie dépendent du point de terminaison ; consultez la référence API
de ce point de terminaison pour connaître ses charges utiles de rappel.

```json
{
  "model": "flux-2-pro-text-to-image",
  "prompt": "A product photograph on a clean studio background",
  "callback_url": "https://your-domain.com/webhooks/runapi"
}
```

## Créer un Callback Secret

Suivez le [Guide d'authentification](https://runapi.ai/fr/docs/guides/authentication.md) pour vous
connecter, puis ouvrez Clés API et créez un secret de rappel pour le compte qui
crée la tâche. Stockez la valeur dans un gestionnaire de secrets et rendez-la
disponible uniquement pour votre récepteur de rappels. Ce n'est pas une clé API et elle ne doit
jamais être envoyée dans une requête de tâche.

Le secret signe les callbacks pour ce compte. Le faire pivoter modifie la
signature pour les livraisons ultérieures ; mettez donc à jour immédiatement chaque récepteur de callback
et gardez les deux valeurs disponibles seulement le temps nécessaire pour traiter
les livraisons en cours.

## Vérifier une signature de callback

Chaque rappel est un HTTP `POST` avec `Content-Type: application/json`.
RunAPI n'ajoute pas d'en-tête `Authorization` à cette requête. Vérifiez
ces en-têtes avant de désérialiser le corps :

| En-tête | Signification |
|----------
| `X-Callback-Id` | Identifiant unique de cette tentative de livraison. |
| `X-Callback-Timestamp` | Horodatage Unix en secondes au moment où la livraison a été signée. |
| `X-Callback-Signature` | Signature HMAC-SHA-256 encodée en Base64. |

Construisez la valeur signée exactement comme suit, en utilisant les octets du corps de la requête non modifiés :

```text
X-Callback-Id + "." + X-Callback-Timestamp + "." + raw request body
```

Décodez en Base64 le Callback Secret, calculez un HMAC-SHA-256 sur cette
valeur, encodez le résultat en Base64 et comparez-le avec
`X-Callback-Signature` à l'aide d'une comparaison à temps constant. Ne parsez pas et
ne re-sérialisez pas le JSON avant de le vérifier.

### JavaScript

```javascript
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyCallback({headers, rawBody, callbackSecret}) {
  const callbackId = headers["x-callback-id"];
  const timestamp = Number(headers["x-callback-timestamp"]);
  const signature = headers["x-callback-signature"];

  if (!callbackId || !signature || !Number.isSafeInteger(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const signedContent = `${callbackId}.${timestamp}.${rawBody}`;
  const expected = createHmac("sha256", Buffer.from(callbackSecret, "base64"))
    .update(signedContent, "utf8")
    .digest();
  const received = Buffer.from(signature, "base64");

  return expected.length === received.length && timingSafeEqual(expected, received);
}
```

Transmettez la chaîne brute du corps de la requête du framework à `rawBody` ; n'appelez pas
`JSON.stringify` sur des données déjà analysées.

### Python

```python
import base64
import hashlib
import hmac
import time


def verify_callback(headers, raw_body, callback_secret):
    callback_id = headers.get("X-Callback-Id")
    timestamp = headers.get("X-Callback-Timestamp")
    signature = headers.get("X-Callback-Signature")

    if not callback_id or not timestamp or not signature:
        return False

    try:
        timestamp = int(timestamp)
        secret = base64.b64decode(callback_secret, validate=True)
        received = base64.b64decode(signature, validate=True)
    except (ValueError, TypeError):
        return False

    if abs(time.time() - timestamp) > 300:
        return False

    signed_content = f"{callback_id}.{timestamp}.{raw_body}".encode("utf-8")
    expected = hmac.new(secret, signed_content, hashlib.sha256).digest()
    return hmac.compare_digest(expected, received)
```

Transmettez la chaîne brute exacte du corps de la requête reçue par votre framework HTTP
en tant que `raw_body`.

## Traiter les livraisons en toute sécurité

* Ne renvoyez une réponse `2xx` qu'après que le callback a été accepté pour
  traitement. Après une réponse non-`2xx` ou un échec de transport, la
  livraison est tentée jusqu'à 10 fois.
* Répondez dans les 15 secondes. Placez les traitements plus lents en file
  d'attente après la vérification plutôt que de bloquer la réponse HTTP.
* Rejetez les callbacks dont les en-têtes de signature sont absents, dont la
  signature est invalide, ou dont l'horodatage est en dehors de la fenêtre de
  tolérance définie par votre récepteur.

## Résoudre les problèmes de vérification

* **Discordance de signature :** confirmez que le Secret de rappel appartient au même compte que la tâche, décodez-le avec Base64 et signez le corps original plutôt que le JSON parsé.
* **En-têtes de signature manquants :** créez un Secret de rappel avant de vous fier aux rappels pour les changements d'état.
* **Horodatage rejeté :** synchronisez l'horloge du récepteur et utilisez une tolérance appropriée à votre déploiement.

Pour les conseils de configuration et de cycle de vie, revenez au [démarrage rapide de l'API Task
](https://runapi.ai/fr/docs/guides/task-api/quickstart.md).

---

## Plus de ressources de RunAPI

- [Accueil](https://runapi.ai/fr/.md)
- [Catalogue de modèles](https://runapi.ai/fr/models.md)
- [Tarifs](https://runapi.ai/fr/pricing.md)
- [Fournisseurs](https://runapi.ai/fr/models)
- [Documentation](https://runapi.ai/fr/docs/guides)
- [SDK](https://runapi.ai/fr/sdk.md)
- [CLI](https://runapi.ai/fr/cli.md)
- [Serveur MCP](https://runapi.ai/fr/mcp.md)
- [Claude Code vs Cursor](https://runapi.ai/fr/claude-code-vs-cursor.md)
- [Configuration de l’API Cursor](https://runapi.ai/fr/cursor-api-setup.md)
- [RunAPI vs OpenRouter](https://runapi.ai/fr/openrouter-alternative.md)
- [Entreprise](https://runapi.ai/fr/contact.md)
- [Contact](https://runapi.ai/fr/contact.md)
- [Conditions](https://runapi.ai/fr/terms.md)
- [Confidentialité](https://runapi.ai/fr/privacy.md)
- [Index du site pour les agents](https://runapi.ai/llms.txt)

Contact: contact@runapi.ai

## Données structurées

```json
[
  {
    "@context": "https://schema.org",
    "inLanguage": "fr",
    "@type": "WebSite",
    "name": "RunAPI",
    "url": "https://runapi.ai/fr",
    "potentialAction": {
      "@type": "SearchAction",
      "target": {
        "@type": "EntryPoint",
        "urlTemplate": "https://runapi.ai/fr/models?q={search_term_string}"
      },
      "query-input": "required name=search_term_string"
    }
  },
  {
    "@context": "https://schema.org",
    "inLanguage": "fr",
    "@type": "Organization",
    "name": "RunAPI",
    "url": "https://runapi.ai/fr",
    "logo": {
      "@type": "ImageObject",
      "url": "https://runapi.ai/fricon.svg"
    },
    "sameAs": [
      "https://github.com/runapi-ai"
    ]
  },
  {
    "@context": "https://schema.org",
    "inLanguage": "fr",
    "@type": "TechArticle",
    "headline": "Rappels",
    "description": "Recevoir et vérifier de manière sécurisée les livraisons de callbacks de Task.",
    "url": "https://runapi.ai/fr/docs/guides/task-api/callbacks",
    "mainEntityOfPage": "https://runapi.ai/fr/docs/guides/task-api/callbacks"
  }
]
```
