Bêta privéeMailbeam est en bêta privée : l'API publique n'est pas encore ouverte.Rejoindre la liste d'attente

Webhooks

Mailbeam peut envoyer un événement en POST à votre serveur, plutôt que de vous obliger à l'interroger.

Mettre en place un endpoint

  1. Allez dans Webhooks dans votre tableau de bord
  2. Saisissez une URL HTTPS et choisissez les événements qui vous intéressent
  3. Copiez le secret de signature — il n'est affiché qu'une fois
  4. 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énementQuand il se déclenche
batch.completedUne tâche par lots a fini son traitement
batch.failedUne tâche par lots s'est arrêtée sur une erreur
quota.thresholdVous 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(), pas express.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.

TentativeDélai
1er réessai5 minutes
2e réessai30 minutes
3e réessai2 heures
4e réessai8 heures
5e réessai24 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.