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.