Webhooks
Mailbeam peut envoyer un événement en POST à votre serveur, plutôt que de vous obliger à l'interroger.
Mettre en place un endpoint
- Allez dans Webhooks dans votre tableau de bord
- Saisissez une URL HTTPS et choisissez les événements qui vous intéressent
- Copiez le secret de signature — il n'est affiché qu'une fois
- Cliquez sur Envoyer un test pour vérifier que votre handler répond
Un endpoint doit être une URL HTTPS joignable publiquement. Nous refusons le HTTP simple, et nous refusons les adresses privées et link-local, parce que ce sont nos serveurs qui font la requête. En développement local, exposez votre machine avec ngrok ou Cloudflare Tunnel.
Événements pris en charge
| Événement | Quand il se déclenche |
|---|---|
batch.completed | Une tâche par lots a fini son traitement |
batch.failed | Une tâche par lots s'est arrêtée sur une erreur |
quota.threshold | Vous atteignez 80 % ou 100 % de votre quota mensuel |
C'est toute la liste. Elle est courte parce que nous n'envoyons que des événements
que nous pouvons observer directement. Deux événements que cette documentation
annonçait autrefois — email.bounced et email.mx_degraded — ont été retirés :
un bounce se produit dans votre flux de courrier, pas dans le nôtre, et nous ne
surveillons pas les enregistrements MX d'un domaine dans le temps. Si l'un des
deux vous serait utile, dites-le-nous et nous regarderons ce qu'il faudrait.
Structure du payload
Tous les payloads de webhook suivent la même enveloppe :
{
"id": "evt_9f2c1d7a-4b83-4a1e-9f0e-2b6c5d8a1f34",
"type": "batch.completed",
"created_at": "2026-08-10T14:32:00Z",
"data": {
"job_id": "job_9f2c1d7a-4b83-4a1e-9f0e-2b6c5d8a1f34",
"total": 5000,
"processed": 5000,
"valid": 4213,
"invalid": 787,
"download_url": "https://api.mailbeam.dev/v1/jobs/job_9f2c1d7a-4b83-4a1e-9f0e-2b6c5d8a1f34/results",
"results_expire_at": "2026-08-13T14:32:00Z"
}
}La livraison est au moins une fois. Nous préférons envoyer un doublon que
perdre un événement : si une réponse se perd sur le chemin du retour, vous verrez
le même événement deux fois. Déduisez les doublons sur id, qui reste stable à
travers tous les réessais et tous les endpoints auxquels l'événement a été envoyé.
Vérification de la signature HMAC
Chaque requête porte un en-tête X-Mailbeam-Signature. Vérifiez-le avant de
faire confiance au payload : l'URL est la seule autre chose qui protège votre
handler, et les URL fuient.
La signature est un condensé HMAC-SHA256 du corps brut de la requête, calculé avec le secret de signature de votre endpoint.
import crypto from "crypto";
export function verifyWebhookSignature(
body: string | Buffer,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
// timingSafeEqual pour éviter les attaques temporelles
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`, "utf8"),
Buffer.from(signature, "utf8")
);
}
// Dans votre handler Express :
app.post("/webhooks/mailbeam", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-mailbeam-signature"] as string;
if (!verifyWebhookSignature(req.body, sig, process.env.MAILBEAM_WEBHOOK_SECRET!)) {
return res.status(400).send("Invalid signature");
}
const event = JSON.parse(req.body.toString());
// Traitement de l'événement…
res.json({ received: true });
});Utilisez
express.raw(), pasexpress.json(). La vérification de signature a besoin des octets bruts du corps. Analyser le JSON d'abord provoque des signatures qui ne correspondent pas.
Politique de réessai
Toute réponse hors de la plage 2xx, ou l'absence de réponse dans les 30 secondes, compte comme une tentative échouée. Les redirections ne sont pas suivies : un 3xx est un échec lui aussi.
| Tentative | Délai |
|---|---|
| 1er réessai | 5 minutes |
| 2e réessai | 30 minutes |
| 3e réessai | 2 heures |
| 4e réessai | 8 heures |
| 5e réessai | 24 heures |
Les réessais sont déclenchés par un ordonnanceur qui tourne toutes les cinq minutes : un délai du tableau est donc un plancher, pas une heure exacte.
Une fois la tentative initiale et les cinq réessais échoués — environ 34 heures — l'endpoint est désactivé et nous envoyons un e-mail au propriétaire du compte. Plus rien n'est mis en file pour lui tant que vous ne l'avez pas réactivé depuis le tableau de bord. Réparez votre récepteur, réactivez-le et envoyez un événement de test pour confirmer avant de compter de nouveau dessus.
Un événement de test envoyé depuis le tableau de bord est une tentative unique. Il n'entre jamais dans l'échelle des réessais et ne désactive jamais un endpoint.
Répondre aux webhooks
Renvoyez un statut 2xx aussi vite que possible. Déportez le traitement lourd dans une file :
app.post("/webhooks/mailbeam", async (req, res) => {
// Vérifiez d'abord la signature
if (!verifyWebhookSignature(req.body, req.headers["x-mailbeam-signature"], secret)) {
return res.status(400).send("Invalid signature");
}
// Accusez réception immédiatement
res.json({ received: true });
// Traitez de façon asynchrone
await queue.add("process-mailbeam-event", JSON.parse(req.body.toString()));
});Votre secret de signature
Le secret est généré à la création de l'endpoint et affiché une seule fois. Nous le stockons chiffré plutôt que haché, parce que, contrairement à une clé d'API, nous devons nous en servir : chaque livraison est signée avec lui. Si vous le perdez, supprimez l'endpoint et créez-en un nouveau.