Webhooks
Payloads de webhook signés, vérification de la signature HMAC-SHA256, calendrier des nouvelles tentatives et logs de livraison.
Les webhooks poussent chaque soumission acceptée vers votre serveur dès son arrivée. Les livraisons sont signées selon le schéma Standard Webhooks, retentées pendant environ trois jours et journalisées tentative par tentative dans le tableau de bord. Des handlers prêts à l'emploi sont dans Recevoir des webhooks.
Ajouter un endpoint
- Dans le tableau de bord, ouvrez Webhooks et choisissez Add endpoint.
- Saisissez une URL HTTPS et choisissez un formulaire, ou tous les formulaires de l'espace de travail.
- Copiez le secret de signature. Il commence par
whsec_et n'est affiché en entier qu'une seule fois. - Envoyez un événement de test pour vérifier votre handler.
URL des endpoints
https:uniquement, sur le port par défaut 443, sans nom d'utilisateur ni mot de passe dans l'URL, jusqu'à 2 048 caractères.- Un nom d'hôte public. Les adresses IP,
localhostet les noms se terminant par.local,.internal,.intranet,.lan,.home.arpaou.onionsont refusés, tout comme ikiform.com, workers.dev et pages.dev. - Le nom d'hôte ne doit résoudre que vers des adresses publiques. Les plages privées, loopback, link-local et carrier-grade NAT sont refusées.
- L'URL est vérifiée à l'enregistrement puis avant chaque livraison. Un endpoint qui se met à résoudre vers une adresse privée est désactivé.
- Un espace de travail peut avoir jusqu'à 3 endpoints. Enterprise n'a pas de limite.
Événements
| Type | Envoyé quand | Retenté |
|---|---|---|
submission.created | Une soumission est acceptée, ou une soumission spam est marquée comme légitime | Oui |
endpoint.test | Vous choisissez Send test sur la page de l'endpoint | Non, une seule tentative |
Le spam ne déclenche jamais de webhook. Les soumissions mises en attente parce que l'espace de travail a dépassé sa limite mensuelle non plus.
La requête
Chaque livraison est un POST avec un corps JSON et trois en-têtes de signature, envoyé par le service de livraison d'Ikiform avec le user agent Ikiform-Webhooks/1.0.
POST /webhooks/ikiform HTTP/1.1
content-type: application/json
user-agent: Ikiform-Webhooks/1.0
webhook-id: dlv_01k7v2d4n8r2p6s0w3y5b7c9e1
webhook-timestamp: 1792300000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=| En-tête | Valeur |
|---|---|
webhook-id | L'id de la livraison. Identique à chaque nouvelle tentative de la même livraison : utilisez-le pour ignorer les doublons |
webhook-timestamp | Secondes Unix de la signature de cette tentative. Nouveau à chaque tentative |
webhook-signature | v1, suivi d'un HMAC-SHA256 en base64. Plusieurs valeurs séparées par des espaces pendant la rotation d'un secret |
Payloads
{
"id": "dlv_01k7v2d4n8r2p6s0w3y5b7c9e1",
"type": "submission.created",
"timestamp": "2026-10-17T12:00:00.000Z",
"api_version": "2026-10-17",
"data": {
"submission_id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3",
"form": { "id": "frm_xxxxxxxxxxxxxxxx", "name": "Contact" },
"fields": { "name": "Ada", "email": "ada@example.com", "message": "Hello" },
"meta": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"origin": "https://example.com",
"referrer": "https://example.com/contact",
"country": "GB",
"subject": null,
"reply_to": "ada@example.com"
},
"created_at": "2026-10-17T12:00:00.000Z",
"truncated": false
}
}| Champ | Description |
|---|---|
id | L'id de la livraison, identique à webhook-id |
type | submission.created ou endpoint.test |
timestamp | La date de création de la livraison, en ISO 8601 UTC. Identique à chaque nouvelle tentative |
api_version | La version du payload avec laquelle l'endpoint a été créé, actuellement 2026-10-17 |
data.submission_id | L'id de la soumission. Récupérez-la avec l'API REST |
data.form | L'id et le name du formulaire |
data.fields | Les valeurs stockées. Avec des champs définis, ce sont les valeurs validées et converties |
data.meta | Les mêmes clés que le meta de l'API REST. Chaque clé est présente et n'importe quelle valeur peut être null. L'IP du visiteur et son hachage ne sont jamais envoyés |
data.meta.user_agent | Le user agent du navigateur, jusqu'à 512 caractères |
data.meta.origin, referrer | La provenance de l'envoi, quand le navigateur les a transmis |
data.meta.country | Code pays à deux lettres, fourni par Cloudflare |
data.meta.subject, reply_to | Issus des champs _subject et _replyto |
data.created_at | La date de stockage de la soumission |
data.truncated | true quand le corps dépasserait 256 Ko, auquel cas fields est vide |
N'importe quelle valeur de meta peut être null. Traitez les valeurs des champs comme des entrées non fiables, exactement comme un envoi de formulaire vers votre propre serveur.
Vérifier la signature
HMAC-SHA256, avec pour clé le secret décodé en base64 après whsec_
webhook-signature: v1,base64(signature)
- Utilisez le corps brut de la requête exactement tel que reçu. Analyser puis resérialiser le JSON modifie les octets.
- L'en-tête peut contenir plusieurs signatures séparées par des espaces. Acceptez la requête si l'une des signatures
v1correspond. - Comparez en temps constant, et rejetez les horodatages éloignés de plus de 5 minutes de l'heure actuelle.
Avec le SDK
verifyWebhook fait tout cela avec Web Crypto et renvoie l'événement analysé, ou lève une WebhookVerificationError avec une reason. Il accepte un objet Headers ou un simple objet comme le req.headers de Node.
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
export async function POST(request: Request) {
const payload = await request.text();
try {
const event = await verifyWebhook({
payload,
headers: request.headers,
secret: process.env.IKIFORM_WEBHOOK_SECRET!,
});
if (event.type === "submission.created") {
console.log(event.data.fields);
}
return new Response(null, { status: 204 });
} catch (error) {
if (error instanceof WebhookVerificationError) {
return new Response(error.reason, { status: 400 });
}
throw error;
}
}Sans le SDK
N'importe quelle bibliothèque Standard Webhooks fonctionne aussi. À la main, il suffit de quelques lignes :
import { createHmac, timingSafeEqual } from "node:crypto";
const secret = Buffer.from(process.env.IKIFORM_WEBHOOK_SECRET.replace(/^whsec_/, ""), "base64");
export function verify(body, headers) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signatures = headers["webhook-signature"];
if (!id || !timestamp || !signatures) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${id}.${timestamp}.${body}`).digest();
return signatures.split(" ").some((entry) => {
const [version, signature] = entry.split(",");
if (version !== "v1" || !signature) return false;
const received = Buffer.from(signature, "base64");
return received.length === expected.length && timingSafeEqual(received, expected);
});
}Vérificateur de signature
Utilise verifyWebhook du SDK avec Web Crypto dans votre navigateur. Rien de ce que vous collez ne quitte cette page.
Répondre
| Votre réponse | Ce que fait Ikiform |
|---|---|
N'importe quel 2xx en moins de 15 secondes | Marque la livraison comme réussie |
410 Gone | Fait échouer la livraison et désactive immédiatement l'endpoint |
429 ou 503 avec Retry-After | Réessaie après ce délai, mais pas plus tard que l'étape suivante du calendrier |
| Une redirection | Non suivie. Compte comme un échec |
| Tout autre statut, un timeout ou une erreur réseau | Réessaie selon le calendrier ci-dessous |
Faites le travail lent après avoir répondu, par exemple dans une file d'attente. Les 4 premiers Ko du corps de votre réponse sont enregistrés dans le log de livraison.
Nouvelles tentatives
Une tentative échouée est retentée selon ce calendrier, chaque délai variant aléatoirement de 20 % au plus dans un sens ou dans l'autre. Après la 9e tentative, environ 3 j 6 h après la première, la livraison est marquée comme échouée.
- 1maintenant0 s
- 25 s5 s
- 35 min5 min
- 430 min35 min
- 52 h2 h 35 min
- 65 h7 h 35 min
- 710 h17 h 35 min
- 81 j1 j 18 h
- 91 j 12 h3 j 6 h
Les barres foncées montrent l'attente la plus courte et les barres claires la plus longue, avec 20 % de jitter dans un sens ou dans l'autre. Après la tentative 9, la livraison est marquée comme échouée.
Endpoints désactivés
Si toutes les livraisons vers un endpoint échouent pendant 5 jours, l'endpoint est désactivé et les propriétaires de l'espace de travail reçoivent un e-mail. Une réponse 410 Gone ou une destination qui devient privée le désactive immédiatement. Réactivez-le depuis la page de l'endpoint une fois le problème corrigé.
Logs de livraison
La page de chaque endpoint liste ses livraisons avec leur statut. Dépliez-en une pour voir chaque tentative : quand elle a eu lieu, si elle était automatique, manuelle ou un test, le code de statut, la durée, et les 4 premiers Ko de votre réponse ou l'erreur. Vous pouvez y relancer n'importe quelle livraison. Les tentatives sont conservées 30 jours et les livraisons 90 jours.
Rotation des secrets
Roll signing secret crée un nouveau secret. Pendant les 24 heures suivantes, chaque livraison est signée à la fois avec l'ancien et le nouveau secret : webhook-signature contient alors deux valeurs, et vous pouvez déployer le nouveau secret sans perdre d'événement.