DocsWebhooksTester les webhooks

Tester les webhooks

Exposez votre endpoint via un tunnel, déclenchez une livraison de test, vérifiez la signature, inspectez et rejouez les livraisons, puis déboguez les échecs.

Webhooks5 min de lectureMis à jour le 10 juin 2026
Télécharger en PDF

Avant de mettre vos webhooks en production, vous voulez la certitude que votre endpoint reçoit bien les livraisons, qu'il vérifie correctement la signature et qu'il renvoie une réponse en moins de dix secondes. Cette page vous guide pas à pas: exposer votre serveur local au moyen d'un tunnel, déclencher une livraison de test avec POST /v1/webhooks/{id}/test, contrôler la signature des deux formats, inspecter et rejouer les livraisons passées, puis déboguer les échecs les plus fréquents. Toutes les requêtes utilisent l'en-tête Authorization: Bearer <clé> et la base https://api.coffrify.com/v1.

1. Exposer votre endpoint local avec un tunnel

Coffrify livre les webhooks vers une URL publique. Pendant le développement, votre serveur tourne sur localhost, qui n'est pas joignable depuis Internet. Un tunnel comme ngrok crée une URL publique temporaire qui redirige vers votre machine. Démarrez d'abord votre serveur (ici sur le port 4242), puis ouvrez le tunnel.

$ ngrok http 4242
 
Forwarding https://a1b2c3d4.ngrok-free.app -> http://localhost:4242

Reprenez l'URL https://...ngrok-free.app affichée et utilisez-la comme url de votre webhook. Vous pouvez créer le webhook directement avec cette URL, en vous abonnant aux événements que vous souhaitez tester. La réponse contient un secret whsec_ affiché une seule fois: notez-le, il sert à vérifier les signatures.

const res = await fetch("https://api.coffrify.com/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.COFFRIFY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
name: "Endpoint local (ngrok)",
url: "https://a1b2c3d4.ngrok-free.app/webhooks/coffrify",
events: ["transfer.downloaded", "transfer.created"],
}),
});
const webhook = await res.json();
console.log(webhook.id, webhook.secret); // whsec_... affiche une seule fois

2. Déclencher une livraison de test

POST/v1/webhooks/{id}/testEnvoie immédiatement une livraison signée à l'URL du webhook et renvoie la réponse du récepteur (code HTTP, durée, aperçu du corps).

Cet appel envoie sur-le-champ une requête signée vers votre URL, sans attendre qu'un vrai événement survienne. Il requiert le scope webhooks:manage. Le corps accepte deux champs optionnels: event_type (une entrée du catalogue d'événements ou ping, valeur par défaut) et data (un objet libre injecté dans la charge utile). La réponse vous donne directement le status HTTP renvoyé par votre serveur, la duration_ms et un body_preview des 500 premiers caractères de sa réponse. Toute livraison de test porte l'en-tête X-Coffrify-Test-Delivery: true, ce qui vous permet de la distinguer d'un événement réel dans votre code.

const res = await fetch(
`https://api.coffrify.com/v1/webhooks/${webhookId}/test`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.COFFRIFY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ event_type: "ping" }),
}
);
console.log(await res.json());
// { webhook_id, event_id, event_type: "ping",
// status: 200, duration_ms: 142, body_preview: "ok",
// error: null, test_delivery: true }

La charge utile reçue par votre endpoint suit cette forme. Le champ test vaut true et type reflète l'event_type demandé. Pour un ping, data est vide; quand vous passez un objet data, il apparaît tel quel ici.

{
"id": "7c9e6b2a-3f10-4d8e-9b21-0a1c2d3e4f56",
"object": "event",
"type": "ping",
"created": 1749552000,
"data": {},
"test": true
}

3. Vérifier la signature

Chaque livraison, y compris les tests, est signée. Vérifiez toujours la signature avant de traiter le corps: c'est ce qui garantit que la requête vient bien de Coffrify et qu'elle n'a pas été altérée. Le secret est au format whsec_, l'algorithme est HMAC-SHA256 et la tolérance d'horodatage est de cinq minutes. Coffrify envoie deux formats de signature en parallèle; choisissez celui qui convient à votre intégration.

FormatEn-têtesMessage signé
Standard Webhookswebhook-id, webhook-timestamp, webhook-signatureid.timestamp.body, résultat encodé en base64 et préfixé v1,
Legacy CoffrifyX-Coffrify-Signaturetimestamp.body, résultat encodé en hexadécimal, en-tête au format t=<ts>,v1=<hex>

Voici comment vérifier le format Standard Webhooks dans un récepteur Express. Recalculez le HMAC sur la concaténation de l'identifiant de livraison, de l'horodatage et du corps brut, puis comparez en temps constant à la signature reçue. Lisez impérativement le corps brut (non parsé), car la moindre reformulation du JSON casserait la comparaison.

import crypto from "node:crypto";
import express from "express";
 
const app = express();
const SECRET = process.env.COFFRIFY_WEBHOOK_SECRET; // whsec_...
 
// On a besoin du corps BRUT, pas du JSON parsé
app.post("/webhooks/coffrify", express.raw({ type: "*/*" }), (req, res) => {
const id = req.header("webhook-id");
const ts = req.header("webhook-timestamp");
const sigHeader = req.header("webhook-signature"); // "v1,<base64>"
const body = req.body.toString("utf8");
 
// Tolérance d'horodatage : 5 minutes
if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) {
return res.status(400).send("timestamp hors tolérance");
}
 
// whsec_<hex> : on décode la partie hexadécimale après le préfixe
const key = Buffer.from(SECRET.slice(6), "hex");
const expected = crypto
.createHmac("sha256", key)
.update(`${id}.${ts}.${body}`)
.digest("base64");
 
const provided = sigHeader.split(",")[1];
const ok =
provided &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
if (!ok) return res.status(401).send("signature invalide");
 
const event = JSON.parse(body);
if (event.test) console.log("livraison de test reçue");
// ... traitez event.type / event.data ...
res.status(200).send("ok");
});

Si vous préférez le format legacy, l'en-tête X-Coffrify-Signature contient l'horodatage et la signature séparés par une virgule. Découpez t= et v1=, recalculez le HMAC sur timestamp.body et comparez la version hexadécimale.

const header = req.header("X-Coffrify-Signature"); // "t=...,v1=..."
const parts = Object.fromEntries(
header.split(",").map((p) => p.trim().split("="))
);
const key = Buffer.from(SECRET.slice(6), "hex");
const expected = crypto
.createHmac("sha256", key)
.update(`${parts.t}.${body}`)
.digest("hex");
const ok = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1)
);

4. Inspecter les livraisons passées

GET/v1/webhooks/{id}/deliveriesListe les tentatives de livraison d'un webhook, de la plus récente à la plus ancienne.

Chaque tentative est journalisée. Pour voir l'historique d'un webhook, listez ses livraisons (scope webhooks:read). Les paramètres limit (100 maximum) et offset paginent les résultats; la réponse renvoie has_more et total. Chaque entrée indique notamment status (pending, success, failed, retrying ou abandoned), status_code, attempt_number, duration_ms, error_message et next_retry_at.

$ curl -s "https://api.coffrify.com/v1/webhooks/$WEBHOOK_ID/deliveries?limit=10" \
-H "Authorization: Bearer $COFFRIFY_API_KEY"

Pour le détail complet d'une tentative, y compris la charge utile envoyée, la signature, le corps de la réponse de votre serveur et ses en-têtes, récupérez la livraison par son identifiant avec GET /v1/webhooks/deliveries/{id}. C'est l'outil idéal pour comprendre précisément ce que votre endpoint a reçu et renvoyé. Pour chercher à travers tous vos webhooks à la fois, GET /v1/webhooks/deliveries/search accepte les filtres event_id, status, webhook_id et since, avec une limit allant jusqu'à 500.

5. Rejouer et relancer une livraison

POST/v1/webhooks/deliveries/{id}/replayRenvoie une livraison passée à la même URL en conservant son identifiant d'événement d'origine.

Quand votre code change ou qu'un bug vous a fait manquer un événement, rejouez la livraison (scope webhooks:manage). Le webhook-id d'origine est conservé, ce qui permet à votre récepteur de dédupliquer correctement; une nouvelle ligne apparaît dans l'historique. Le webhook doit être actif, sinon l'appel renvoie une erreur 409: réactivez-le avant de rejouer. La réponse contient le status HTTP obtenu, la duration_ms et un body_preview.

const res = await fetch(
`https://api.coffrify.com/v1/webhooks/deliveries/${deliveryId}/replay`,
{
method: "POST",
headers: { Authorization: `Bearer ${process.env.COFFRIFY_API_KEY}` },
}
);
console.log(await res.json());
// { replayed: true, delivery_id, new_delivery_id,
// event_id, event_type, status: 200, duration_ms, body_preview }

Le rejeu et la relance répondent à deux besoins distincts. Le rejeu envoie toujours, immédiatement, même une livraison déjà réussie: pratique pour retester après une correction. La relance, POST /v1/webhooks/deliveries/{id}/retry, n'agit que sur une livraison qui n'a pas abouti (failed ou abandoned): elle remet le compteur à zéro et replanifie la tentative pour le mécanisme de réessai automatique. Relancer une livraison déjà réussie renvoie une 409 qui vous oriente vers le rejeu.

6. Déboguer les échecs

Quand une livraison ne passe pas, le champ error_message de la livraison et le body_preview renvoyé par l'appel de test vous donnent presque toujours la cause. Voici les symptômes les plus courants et la marche à suivre.

SymptômeCause probableQue faire
status = 0 avec une erreur réseauURL injoignable, tunnel fermé ou délai de 10 s dépasséVérifiez que le tunnel tourne et que votre serveur répond vite. Renvoyez 200 immédiatement et traitez en tâche de fond
Réponse 401 de votre serveurVérification de signature qui échoueLisez le corps brut, décodez le secret whsec_ depuis sa partie hexadécimale, vérifiez l'horodatage (tolérance 5 min)
status_code 4xx ou 5xxVotre endpoint lève une erreur applicativeInspectez la livraison complète via GET /v1/webhooks/deliveries/{id} pour voir response_body
Aucune livraison ne partWebhook désactivé ou non abonné à l'événementContrôlez is_active et la liste events; testez avec simulate qui indique subscribed_to_event_type

Cette page vous a-t-elle aidé ?
Anonyme, dédupliqué 24h par signature locale.