API
Webhooks
Le format des messages que Mail envoie à ton application, leurs en-têtes, la signature à vérifier, les reprises et les événements.
Mail envoie ses webhooks au format Standard Webhooks, le même que Svix et Resend : une bibliothèque de ce standard, dans le langage de ton application, vérifie la signature à ta place. Pour créer une adresse, voir Recevoir les réponses d’un formulaire par webhook.
Événements
| Événement | Quand |
|---|---|
form.response.created | Quelqu’un envoie un formulaire. |
Le message
Un POST en JSON, 256 Ko au plus.
{ "type": "form.response.created", "timestamp": "2026-10-17T09:12:03.000Z", "data": { "id": "k57d…", "form_id": "j97a…", "form_public_id": "k7m2p9x4q8ab", "form_name": "Contact", "values": { "prenom": "Léa", "email": "lea@studio.ch", "message": "Un devis, s'il vous plaît." }, "email": "lea@studio.ch", "consent": { "status": "pending", "text": "J'accepte de recevoir vos nouvelles", "at": "2026-10-17T09:12:03.000Z" }, "page": "https://monsite.ch/contact", "created_at": "2026-10-17T09:12:03.000Z" }}| Champ | Sens |
|---|---|
data.id | Identifiant de la réponse. Le même que dans GET /forms/:id/responses. |
data.values | Une valeur par champ, avec la variable du champ comme clé. Une case cochée vaut oui. |
data.email | L’adresse du champ e-mail, ou null. |
data.consent | La preuve de consentement : status vaut pending (en attente du lien de confirmation) ou confirmed. null si la case n’est pas cochée. |
data.page | La page du site d’où vient la réponse, sans paramètres. |
En-têtes
| En-tête | Contenu |
|---|---|
webhook-id | Identifiant du message, msg_…. Identique à chaque essai et à chaque renvoi. |
webhook-timestamp | Heure d’envoi, en secondes. Renouvelée à chaque essai. |
webhook-signature | v1, suivi de la signature en base64. |
Vérifier la signature
- Refuse le message si
webhook-timestamps’écarte de plus de 5 minutes de l’heure de ton serveur. C’est ce qui empêche de rejouer un message volé. - Prends le secret
whsec_…, enlève le préfixewhsec_et décode le reste en base64 : c’est la clé. - Calcule le HMAC-SHA256 de
webhook-id, un point,webhook-timestamp, un point, puis le corps brut de la requête (avant toute lecture du JSON). - Encode le résultat en base64 et compare-le, en temps constant, à chaque signature de
webhook-signature(séparées par une espace, chacune préfixéev1,).
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64")const signed = `${id}.${timestamp}.${rawBody}`const expected = crypto.createHmac("sha256", key).update(signed).digest()const ok = signatureHeader.split(" ").some((part) => { const [version, value] = part.split(",") const received = Buffer.from(value ?? "", "base64") return version === "v1" && received.length === expected.length && crypto.timingSafeEqual(received, expected)})Réponds 401 si la signature est fausse. Réponds 2xx quand le message est traité ou déjà vu.
Livraison
| Succès | Une réponse 2xx en 10 secondes. |
| Redirection | Un échec : Mail ne suit jamais une redirection. Donne l’adresse finale. |
| Essais | 8 au total : tout de suite, puis après 5 secondes, 5 minutes, 30 minutes, 2 heures, 5 heures, 10 heures, 10 heures. |
| Renvoi | Depuis la page Webhooks, bouton Livraisons puis Renvoyer. Même webhook-id. |
| Journal | 30 jours. |
Adresses acceptées
https uniquement, port 443, un nom de domaine public. Sont refusés : http, un numéro IP, localhost et les noms internes (.local, .internal…), un nom qui pointe vers une adresse privée, une adresse avec identifiant et mot de passe. Chaque espace garde 20 adresses au plus.
Cette page t'a aidé ?
Merci, c'est noté.