Les événements
Un webhook est une adresse de votre serveur à laquelle nous envoyons un événement dès qu’il se produit. Il existe six événements :
form.response.created Eine neue Antwort auf eines deiner Formulare
form.response.updated Jemand hat seine Antwort über den Bearbeitungslink geändert
order.paid Eine Bestellung ist bezahlt
order.fulfilled Eine Bestellung ist versendet oder übergeben
order.canceled Eine bestätigte Bestellung wurde storniert
order.refunded Eine Bestellung wurde erstattetorder.refunded arrive lors du premier remboursement partiel et lors du remboursement complet. Une session de paiement qui n'a jamais été payée et qui expire n'est pas une annulation — aucun événement n'est envoyé dans ce cas.
Configurer un webhook
Les programmes peuvent aussi enregistrer eux-mêmes des webhooks : POST /v1/webhooks avec une clé qui porte webhooks:manage — ainsi que le droit de lecture des données envoyées par le webhook (forms:read pour les événements de formulaire, orders:read pour les commandes).
- Ouvrez l'espace Développeur dans le tableau de bord.
- Sous « Webhooks », saisissez l'adresse de votre destinataire — elle commence par https.
- Sous « Quels événements ? », choisissez ce que vous voulez recevoir, puis cliquez sur « Créer un webhook ».
- Copiez dans votre destinataire le secret qui s'affiche ensuite.
- Cliquez sur « Envoyer un test » — le résultat s'affiche aussitôt en dessous.
Ce qui arrive
Chaque événement est un POST avec du JSON. data.object contient l'objet sous la même forme que dans l'API — une commande comme avec GET /v1/orders/{id}, une réponse comme dans la liste des réponses. Pour les événements de formulaire, data.form indique en plus à quel formulaire la réponse appartient.
{
"id": "evt_Q2x9kLmP4vT7aB1cD3eF5gH7",
"object": "event",
"type": "order.paid",
"api_version": "2026-09-23",
"created_at": "2026-09-23T14:05:12Z",
"data": {
"object": {
"id": 5531,
"object": "order",
"number": 42,
"status": "confirmed",
"payment_status": "paid",
"currency": "EUR",
"total": 3890,
"customer": { "email": "mara@example.com", "name": "Mara Sol" },
"items": [
{ "name": "Kaffeebohnen, 500 g", "sku": "KAF-500", "quantity": 2, "unit_price": 1690 }
]
}
}
}Vérifier la signature
Chaque appel porte l'en-tête KENJTEC-Signature: t=<heure>,v1=<signature>. La signature est un HMAC-SHA256 calculé sur l'heure, un point et le corps non modifié de la requête — avec le secret de votre webhook comme clé. Rejetez tout ce dont l'heure remonte à plus de cinq minutes.
Vérifiez sur le corps brut avant de lire le JSON : un seul espace différent donne une autre signature. Vous trouvez le secret à tout moment sous « Afficher le secret » ; « Renouveler le secret » rend l'ancien immédiatement invalide.
import crypto from "node:crypto";
// raw: the unparsed request body (Buffer), header: the KENJTEC-Signature header
export function verify(raw, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(`${parts.t}.`).update(raw).digest("hex");
const given = String(parts.v1 ?? "");
return given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));
}import hashlib, hmac, time
def verify(raw: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Le même événement peut arriver deux fois — par exemple lorsque votre serveur répond mais que la réponse ne nous parvient pas. L'identifiant dans id (également dans l'en-tête KENJTEC-Event-Id) reste alors le même : mémorisez-le et ne traitez chaque événement qu'une seule fois.
Quand quelque chose n'arrive pas
Toute réponse avec un statut de 200 à 299 reçue dans les dix secondes est considérée comme livrée. Nous ne suivons pas les redirections.
Si un événement n'arrive pas, nous réessayons : après 1, 5 et 30 minutes, après 2, 6 et 12 heures, puis deux fois après 24 heures — pendant un peu plus de trois jours. Si cinq événements de suite échouent définitivement, nous désactivons le webhook ; le tableau de bord indique alors pourquoi, et « Réactiver » le réinitialise.
Sous « Journal », vous voyez chaque livraison des 30 derniers jours avec son statut et son contenu. De là, vous pouvez renvoyer n'importe quel événement avec « Renvoyer ».

Les webhooks ne sont envoyés qu'à des adresses https publiques. Nous n'acceptons pas les adresses de réseaux internes ni les adresses de KENJTEC lui-même.