Recevoir des webhooks et vérifier les signatures
Ody envoie les événements de l'espace de travail — nouveaux messages, résultats d'appels, changements de contact — vers les points de terminaison HTTPS que vous enregistrez via l'API, chaque livraison étant signée selon la spécification Standard Webhooks afin que vous puissiez prouver qu'elle provient d'Ody.
Avant de commencer
- Vous avez besoin d'une clé API avec la portée
webhooks:manage— voir Obtenir votre clé API Ody. - Votre point de terminaison doit être une URL HTTPS qui répond rapidement avec un statut
2xx(les livraisons expirent après 10 secondes). - Chaque espace de travail peut enregistrer jusqu'à 50 points de terminaison.
Étape par étape
- Listez les types d'événements auxquels vous pouvez vous abonner :
curl https://api.ody.co/v1/api/webhook-events \
-H "Authorization: Bearer ody_live_…"
Le catalogue aujourd'hui : message.received, message.delivered, message.failed, call.ringing, call.answered, call.completed, call.forwarded, call.missed, call.recording.completed, call.summary.completed, call.transcript.completed, call.voicemail.completed, call.flow.started, call.flow.completed, call.flow.aborted, contact.updated, contact.deleted. S'abonner à "*" couvre tous les types d'événements actuels et futurs.
Les événements call.flow.* se déclenchent lorsqu'un appel est répondu par un flux d'appels : started lorsque l'appelant entre dans le flux, completed lorsqu'il atteint une destination (avec le type de destination — sonnerie, assistant IA, messagerie vocale, transfert ou raccrochage), et aborted si le flux n'a pas pu se terminer.
- Enregistrez votre point de terminaison :
curl https://api.ody.co/v1/api/webhooks \
-H "Authorization: Bearer ody_live_…" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/ody-webhook","events":["message.received"],"label":"prod"}'
- Stockez immédiatement le
secretde la réponse. Le secret de signaturewhsec_…n'est renvoyé que lors de la création et de la rotation — il n'est plus jamais affiché. - Envoyez-vous un événement de test signé et confirmez que votre point de terminaison le vérifie et l'accepte :
curl -X POST https://api.ody.co/v1/api/webhooks/WEBHOOK_ID/test \
-H "Authorization: Bearer ody_live_…"
Le test publie un événement {"type":"ping"} signé exactement comme une livraison réelle.
À quoi ressemble une livraison
Chaque livraison est une requête POST avec un corps JSON et trois en-têtes de signature :
{
"id": "evt_…",
"apiVersion": "2026-06-01",
"createdAt": "2026-08-16T12:00:00.000Z",
"type": "message.received",
"data": { "conversationId": "…", "activityId": "…", "numberE164": "+1…" }
}
| En-tête | Signification |
|---|---|
webhook-id |
L'identifiant de l'événement — dédupliquez-le, car les nouvelles tentatives le réutilisent |
webhook-timestamp |
Secondes Unix au moment de l'envoi |
webhook-signature |
v1,<base64 HMAC-SHA256> sur <id>.<timestamp>.<raw body> |
Vérification de la signature
La clé HMAC est la partie décodée en base64 de votre secret après le préfixe whsec_, et le message signé est <webhook-id>.<webhook-timestamp>.<raw body>. Vérifiez toujours par rapport aux octets bruts de la requête — la re-sérialisation du JSON analysé rompt la signature. Rejetez les horodatages de plus de quelques minutes pour bloquer les relectures.
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
app.post("/ody-webhook", express.raw({ type: "application/json" }), (req, res) => {
const secret = process.env.ODY_WEBHOOK_SECRET; // whsec_…
const id = req.headers["webhook-id"];
const timestamp = req.headers["webhook-timestamp"];
const signature = req.headers["webhook-signature"];
// Replay guard: reject deliveries older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.status(401).end();
const key = Buffer.from(secret.slice("whsec_".length), "base64");
const expected = "v1," + createHmac("sha256", key)
.update(`${id}.${timestamp}.${req.body}`)
.digest("base64");
const a = Buffer.from(expected);
const b = Buffer.from(String(signature));
if (a.length !== b.length || !timingSafeEqual(a, b)) return res.status(401).end();
const event = JSON.parse(req.body.toString("utf8"));
console.log("verified event:", event.type);
res.status(200).end();
});
Les SDKs officiels l'incluent sous forme d'une seule ligne — verifyWebhookSignature (TypeScript) et verify_webhook_signature (Python), tous deux avec une comparaison à temps constant et la protection anti-relecture de 5 minutes intégrée. Voir Démarrage rapide des SDK : TypeScript et Python.
Nouvelles tentatives, historique et gestion
- Nouvelles tentatives : une livraison échouée (non-2xx, délai d'attente ou erreur de connexion) est réessayée environ 5 secondes puis 30 secondes après la première tentative — trois tentatives au total, avec un délai d'attente de 10 secondes chacune.
- Historique :
GET /v1/api/webhooks/:id/deliveriesliste les livraisons récentes, les plus récentes en premier, avec les codes de statut, les erreurs et les durées par tentative — votre premier arrêt lorsque des événements semblent manquants. - Gérer les points de terminaison :
GET /v1/api/webhooksles liste (jamais le secret),PATCH /v1/api/webhooks/:idmet à joururl,events,labeloustatus(enabled/disabled), etDELETE /v1/api/webhooks/:iden supprime un ainsi que son historique de livraison. - Faire pivoter le secret à tout moment avec
POST /v1/api/webhooks/:id/rotate— le nouveauwhsec_…est renvoyé une seule fois, et les anciennes signatures cessent d'être valides immédiatement.
Dépannage
- La vérification de la signature échoue toujours — vous vérifiez probablement un corps re-sérialisé. Utilisez les octets bruts de la requête et confirmez que vous avez décodé le secret en base64 après avoir supprimé
whsec_. - Aucune livraison n'arrive — vérifiez que le
statusdu point de terminaison estenabled, que sa listeeventsinclut le type que vous attendez (ou"*"), et l'historique des livraisons pour les erreurs par tentative. - Événements en double — les nouvelles tentatives réutilisent le même
webhook-id. Dédupliquez-les. webhook limit reached— vous avez atteint 50 points de terminaison pour l'espace de travail ; supprimez-en un inutilisé d'abord.
Articles connexes
Questions fréquemment posées
Comment les livraisons de webhook sont-elles signées ?
Selon la spécification Standard Webhooks : un en-tête webhook-signature de la forme v1,<base64 HMAC-SHA256> calculé sur '<webhook-id>.<webhook-timestamp>.<raw body>' avec votre secret whsec_.
Que se passe-t-il si mon point de terminaison est hors service ?
Ody réessaie deux fois — environ 5 secondes puis 30 secondes après le premier échec. Vérifiez GET /v1/api/webhooks/:id/deliveries pour les codes de statut et les erreurs par tentative.
J'ai perdu mon secret de signature — que faire maintenant ?
Faites-le pivoter avec POST /v1/api/webhooks/:id/rotate. Le nouveau secret whsec_ est renvoyé une seule fois ; les anciennes signatures cessent d'être valides immédiatement.