10 min de lecture

Webhooks et idempotence : éviter les doublons sans sur-architecturer

Idempotency-Key, stockage minimal, retries HTTP et sécurité basique — code PHP illustratif et pièges fréquents.

  • API
  • Webhooks
  • Fiabilité
Schéma : livraisons webhook avec retries Retries & idempotence Émetteur POST + Idempotency-Key Récepteur 200 rapide + traitement asynchrone recommandé

Contrat HTTP minimal

  • Répondre vite (2xx) si la charge est lourde — traitez en asynchrone derrière une file.
  • Valider la signature / secret partagé avant toute écriture métier.
  • Journaliser le corps brut (hash + identifiant) pour le support, pas nécessairement le PII complet.

Idempotence : schéma de table minimal

Exemple SQL (MySQL / MariaDB) : une ligne par clé vue, statut du traitement.

CREATE TABLE webhook_inbox (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  provider VARCHAR(32) NOT NULL,
  idempotency_key VARCHAR(128) NOT NULL,
  payload_hash CHAR(64) NOT NULL,
  status ENUM('received','processed','failed') NOT NULL DEFAULT 'received',
  created_at DATETIME NOT NULL,
  UNIQUE KEY uq_provider_key (provider, idempotency_key)
);

Flux PHP (lecture rapide)

$key = $request->headers->get('Idempotency-Key') ?? $payload['event_id'] ?? null;
if (!$key) {
    return new JsonResponse(['title' => 'Missing idempotency key'], 400);
}

$hash = hash(‘sha256’, $request->getContent()); $row = $db->findInboxRow($provider, $key);

if ($row && $row[‘payload_hash’] !== $hash) { return new JsonResponse([‘title’ => ‘Conflict: same key, different body’], 409); }

if ($row && $row[‘status’] === ‘processed’) { return new JsonResponse([‘status’ => ‘duplicate_ignored’], 200); }

$db->upsertInbox($provider, $key, $hash); $bus->dispatch(new ProcessWebhook($provider, $key)); return new JsonResponse([‘status’ => ‘accepted’], 202);

Retries et ordre

Si les événements doivent être ordonnés par ressource, incluez un sequence ou un horodatage fiable dans le payload et ignorez les doublons / replays obsolètes via comparaison monotone (par compte, par agrégat, etc.).

FAQ

202 vs 200 ?

202 Accepted si le travail est poussé en asynchrone ; 200 si tout est traité dans la requête. Les deux sont valides ; soyez cohérents et documentez-le.

Et sans idempotency key du fournisseur ?

Dérivez une clé stable du payload (event_id + type) après validation de schéma ; refusez les événements sans identifiant stable — c’est un signal de mauvaise conception côté émetteur.