Aller au contenu principal

Webhook multicanal

Chaque réponse, sur chaque canal,
dans un seul webhook.

WhatsApp depuis votre numéro, email depuis votre boîte, SMS depuis votre téléphone : un seul endpoint les reçoit tous dans la même enveloppe JSON signée. Un handler alimente votre boîte de réception, votre agent IA ou votre CRM.

Pressé ? Résumez cet article avec une IA

La réponse courte

Un seul webhook peut-il recevoir WhatsApp, email et SMS ?

Oui. Unlimited Messaging envoie chaque message reçu sur votre numéro WhatsApp relié, votre boîte mail connectée et la SIM de votre téléphone Android vers une seule URL, sous le même événement, message.received, signé selon Standard Webhooks. L'enveloppe ne change jamais ; data.channel.type indique WHATSAPP, EMAIL ou SMS. Les webhooks sont inclus dans les offres payantes Medium et Max.

Assemblée à partir de fournisseurs séparés, la même boîte de réception demande trois intégrations. La Cloud API de Meta envoie du JSON imbriqué signé en X-Hub-Signature-256 après une poignée de main hub.challenge. Twilio envoie SMS et WhatsApp en paramètres form-encoded signés en HMAC-SHA1. SendGrid Inbound Parse envoie du multipart/form-data pour un nom d'hôte dont vous avez déplacé le MX. Trois parsers, trois vérifications de signature, trois idées de ce qu'est une conversation.

Ici, les canaux sont des services à côté d'une seule API, et cette API annonce chaque message de la même façon : id, direction, content, sender, sentAt, la conversation (un chat ou un groupe WhatsApp, la racine d'un fil d'emails, un numéro SMS) et le compte qui l'a reçu. L'email ajoute subject, html et attachments ; WhatsApp ajoute les médias, les groupes, les réactions et les modifications. Les règles de livraison sont communes : répondre 2xx en moins de 10 secondes, 8 essais sur un peu plus de 3 jours, dédupliquer sur eventId.

Les réponses repartent des mêmes identités. replyToMessageId répond sur place dans un chat WhatsApp ou un fil d'emails ; une réponse SMS désigne la SIM. Vos clients voient votre numéro et votre adresse, jamais ceux d'un fournisseur.

À lire aussi : envoyer email et WhatsApp en une seule requête API, et réunir la prospection WhatsApp et email dans une seule boîte.

Illustration : deux serveurs qui échangent des événements webhook.

Les événements

À quoi ressemble le payload du webhook multicanal ?

Le même événement message.received sur chaque canal. Ce qui change : data.channel.type et quelques champs propres au canal.

  1. Commun à tous les canaux

    type, eventId, occurredAt ; data.message avec id, externalId, direction, fromMe, content, sentAt, receivedAt, sender, senderId ; data.channel avec type, account (id, name) et conversation (id, isGroup, address).

  2. WHATSAPP

    Médias (mediaUrl, mediaType, mimeType), localisations, réponses citées (replyTo), groupes (participant, conversation.participants), plus message.reaction, message.edited et message.deleted.

  3. EMAIL

    subject, html (non fiable, à nettoyer), attachments (20 par email au plus), conversation.id égal à la racine du fil et replyTo au message auquel on répond.

  4. SMS

    Texte uniquement. conversation.id vaut null : regroupez par sender. Messages reçus après la connexion de l'application Android.

  5. Autour des messages

    message.sent et message.status suivent ce que vous envoyez sur les trois canaux ; account.status_changed signale un numéro, une boîte ou une SIM qui décroche.

Trois canaux, une enveloppeJSON
// WhatsApp
{ "type": "message.received", "eventId": "whd_6f1c...", "occurredAt": "2026-11-09T10:00:01.000Z",
  "data": {
    "message": { "id": "msg_0e55...", "direction": "IN", "content": "Is my order ready?",
                 "sender": "+447700900123", "senderName": "Lea", "sentAt": "2026-11-09T10:00:00.000Z" },
    "channel": { "type": "WHATSAPP", "account": { "id": "acc_373b...", "name": "Support" },
                 "conversation": { "id": "[email protected]", "isGroup": false,
                                   "address": "+447700900123" } } } }

// Email: same envelope, plus subject, html and attachments
{ "type": "message.received", "eventId": "whd_9a4f...", "occurredAt": "2026-11-09T14:05:12.000Z",
  "data": {
    "message": { "id": "msg_3f8a...", "direction": "IN", "content": "Signed quote attached.",
                 "sender": "[email protected]", "subject": "Re: Quote #1042",
                 "html": "<p>Signed quote attached.</p>", "attachments": [ { "filename": "quote.pdf" } ] },
    "channel": { "type": "EMAIL", "account": { "id": "acc_8b2e...", "name": "[email protected]" },
                 "conversation": { "id": "[email protected]", "isGroup": false,
                                   "address": "[email protected]" } } } }

// SMS: text only, no conversation id
{ "type": "message.received", "eventId": "whd_2b9e...", "occurredAt": "2026-11-09T08:42:06.000Z",
  "data": {
    "message": { "id": "msg_7d2c...", "direction": "IN", "content": "Running 10 min late, sorry!",
                 "sender": "+447700900456", "sentAt": "2026-11-09T08:42:03.000Z" },
    "channel": { "type": "SMS", "account": { "id": "acc_5e8d...", "name": "Front desk SIM" },
                 "conversation": { "id": null, "isGroup": false, "address": "+447700900456" } } } }
Des corps message.received abrégés, d'après le schéma publié, un par canal. L'événement complet porte tous les champs listés à gauche.

Mise en place

Comment écrire un seul handler pour tous les canaux ?

Reliez les trois canaux une fois, enregistrez un endpoint, et ne différenciez que là où un canal diffère vraiment.

  1. 01

    Reliez vos canaux

    Scannez un QR code pour WhatsApp, connectez votre boîte en IMAP/SMTP, installez l'application Android pour le SMS. Chacun devient un compte.

  2. 02

    Enregistrez un seul endpoint

    POST /webhooks avec votre URL et vos eventTypes, ou ajoutez-le sur la page Webhooks du tableau de bord. Sans accountIds, il couvre tous les comptes, même futurs.

  3. 03

    Vérifiez une fois, différenciez tard

    Une vérification de signature, un upsert sur eventId, une ligne de boîte de réception. Seul l'appel de réponse diffère : replyToMessageId pour WhatsApp et l'email, la SIM pour le SMS.

POST /webhookscurl
curl https://api.unlimitedmessaging.app/webhooks \
  -H "Authorization: Bearer $UM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/um",
    "eventTypes": ["message.received", "message.status", "account.status_changed"],
    "description": "Every reply, every channel"
  }'

# No accountIds: the endpoint covers every WhatsApp number, mailbox and SIM you have,
# including ones linked later. The response carries "secret": "whsec_..." once.
Il n'existe pas d'option « tous les événements » : listez les types voulus. Un type ajouté plus tard n'est jamais activé sur les endpoints existants ; envoyez la liste complète avec PATCH /webhooks/{id} pour en ajouter un.
app/api/webhooks/um/route.tsNext.js route handler
// app/api/webhooks/um/route.ts (Next.js App Router)
import crypto from 'node:crypto';
import { UnlimitedMessagingApiClient } from '@unlimited-messaging/sdk';

const um = new UnlimitedMessagingApiClient({ token: process.env.UM_API_KEY! });
const key = Buffer.from(process.env.UM_WEBHOOK_SECRET!.replace(/^whsec_/, ''), 'base64');

// Standard Webhooks: HMAC-SHA256 of "id.timestamp.rawBody", base64.
function verify(raw: string, id: string, ts: string, header: string) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.${raw}`).digest('base64');
  return header.split(' ').some((entry) => {
    const sig = entry.startsWith('v1,') ? entry.slice(3) : '';
    return sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}

export async function POST(req: Request) {
  const raw = await req.text();
  const h = req.headers;
  if (!verify(raw, h.get('webhook-id') ?? '', h.get('webhook-timestamp') ?? '',
    h.get('webhook-signature') ?? '')) {
    return new Response('bad signature', { status: 401 });
  }

  const event = JSON.parse(raw);
  if (event.type !== 'message.received') return new Response(null, { status: 200 });

  const { message: m, channel } = event.data;
  // One inbox row, whatever the channel (upsert on eventId: delivery is at least once).
  await inbox.upsert({
    eventId: event.eventId,
    channel: channel.type,                       // WHATSAPP | EMAIL | SMS
    from: m.sender ?? m.senderId,                // +E164 or email address
    thread: channel.conversation.id ?? m.sender, // SMS has no conversation id
    subject: m.subject ?? null,
    text: m.content,
    at: m.sentAt,                                // order on this, not on arrival
  });

  const answer = await draftReply(m.content);    // your LLM call, or null for a human
  if (answer) {
    if (channel.type === 'SMS') {
      await um.messages.messageSend({
        to: { phone: m.sender },
        channels: [{ channel: 'SMS', accountId: channel.account.id }],
        text: answer,
      });
    } else {
      // WhatsApp and email: same chat or same thread, same number or mailbox.
      await um.messages.messageSend({ replyToMessageId: m.id, text: answer });
    }
  }
  return new Response(null, { status: 200 }); // within 10 s; queue slow work
}
inbox et draftReply sont à vous (une table, un appel LLM). La vérification suit le schéma Standard Webhooks documenté, identique pour tous les canaux.

Vibe coding

Vous codez avec Cursor, Claude Code, Lovable ou Bolt ? Collez ceci

Assez de contexte pour que votre éditeur IA construise toute la route de la boîte unifiée en une passe.

Prompt à copier
Add a unified inbox to this app with Unlimited Messaging: WhatsApp (my number), email (my mailbox) and SMS (my Android phone) all arrive on ONE webhook.

1. Create a POST route at /api/webhooks/um that reads the RAW body.
2. Verify the Standard Webhooks signature: key = base64-decode(UM_WEBHOOK_SECRET without "whsec_"); expected = base64(HMAC-SHA256(key, `${webhook-id}.${webhook-timestamp}.${rawBody}`)); accept if any space-separated entry of the webhook-signature header equals `v1,${expected}` (constant-time compare); reject timestamps more than 5 minutes off; return 401 otherwise.
3. On type "message.received", upsert one row keyed on body.eventId: channel = data.channel.type (WHATSAPP | EMAIL | SMS), from = data.message.sender ?? data.message.senderId, thread = data.channel.conversation.id ?? data.message.sender, subject = data.message.subject, text = data.message.content, at = data.message.sentAt (order on this, never on arrival).
4. Build an inbox page listing threads across channels with a channel badge.
5. Replies: install @unlimited-messaging/sdk, read UM_API_KEY from env. WhatsApp and email: `client.messages.messageSend({ replyToMessageId: data.message.id, text })`. SMS: `client.messages.messageSend({ to: { phone: sender }, channels: [{ channel: 'SMS', accountId: data.channel.account.id }], text })`.
6. Return 200 within 10 seconds; queue slow work. Put UM_API_KEY and UM_WEBHOOK_SECRET in .env and .env.example.

Docs: https://docs.unlimitedmessaging.app (Webhooks). Register with POST https://api.unlimitedmessaging.app/webhooks and eventTypes ["message.received", "message.status", "account.status_changed"].

Ce que l'on construit

Que construire avec un seul webhook pour tous les canaux ?

Trois projets qui demandent trois intégrations ailleurs, et un seul handler ici.

Une boîte de support multicanale

Une liste de conversations, un badge par canal, des réponses envoyées depuis votre numéro ou votre adresse dans le même fil. Ajoutez message.status pour afficher distribué et lu quand le canal le signale.

Un répondeur IA

Donnez content à Claude ou GPT avec l'historique de la conversation, renvoyez la réponse sur le canal d'origine, passez la main à un humain sur mot-clé ou en cas de doute.

Synchro CRM

Rattachez chaque message au contact par téléphone ou adresse email, quel que soit le canal, avec sentAt pour la chronologie.

  1. Bascule de canal

    Pas de réponse sur WhatsApp au bout d'un jour ? Relancez par email ou SMS ; le même POST /message peut envoyer sur trois canaux à la fois.

  2. Supervision

    account.status_changed sur les trois canaux vous dit quand un numéro, une boîte ou un téléphone cesse de relayer.

  3. Flux no-code

    Pointez le webhook vers n8n, Make ou Zapier et aiguillez sur data.channel.type.

Le tableau

Une boîte, trois façons : Twilio, une pile maison ou Unlimited Messaging

Ce qu'il faut pour recevoir les réponses WhatsApp, email et SMS dans le même code.

Une boîte, trois façons : Twilio, une pile maison ou Unlimited Messaging
Twilio + SendGridUn fournisseur, plusieurs produitsMeta + service de parsing + fournisseur SMSFait maisonUnlimited MessagingUn webhook, offres payantes
Formats de payloadForm-encoded pour SMS et WhatsApp, multipart pour l'emailJSON de Meta, format du fournisseur, multipart ou JSON pour l'emailUne seule enveloppe JSON
Vérifications de signatureX-Twilio-Signature (HMAC-SHA1), plus la protection propre à l'endpoint de parsingX-Hub-Signature-256 pour Meta, une par autre fournisseurUne seule vérification Standard Webhooks
Identités vues par vos clientsNuméros loués chez Twilio, un expéditeur WhatsApp enregistréUn numéro basculé sur la Cloud API, un numéro SMS louéVotre numéro, votre boîte et votre SIM
Mise en place avant le premier messageAchat de numéro, enregistrement de l'expéditeur WhatsApp, MXApp Meta et portefeuille business, MX, location de numéroScan de QR code, mot de passe de la boîte, application Android
Frais par message0,0083 $ par segment SMS reçu aux États-Unis, 0,005 $ par message WhatsApp plus frais MetaFrais Meta au-delà de 1 000 réponses de service par numéro et par mois, frais par SMSAucun ; webhooks avec Medium ou Max
Statut officielWhatsApp Business Platform officielleWhatsApp Business Platform officielleWhatsApp par le protocole non officiel des appareils connectés

Vérifié le 9 novembre 2026 sur les pages Twilio (webhooks, tarifs SMS et WhatsApp), la documentation Meta (webhooks, tarifs) et le guide Inbound Parse de SendGrid (liens dans les sources).

Les contreparties

Quelles sont les limites d'un webhook unifié sur vos propres canaux ?

Un seul format supprime le code de colle. Chaque canal garde sa nature.

  1. Les webhooks sont payants

    L'envoi sur les trois canaux est gratuit sur toutes les offres, et les messages reçus s'affichent dans la boîte de l'application web. Les pousser vers votre serveur demande Medium (19 € par mois, ou 17 € par mois en annuel) ou Max.

  2. WhatsApp est un appareil connecté

    Le numéro est relié comme WhatsApp Web, par un protocole non officiel : le spam le fait restreindre, et WhatsApp déconnecte les appareils reliés si le téléphone principal reste hors ligne 14 jours.

  3. Le SMS demande un téléphone Android

    La SIM relaie via l'application Android gratuite (8.0 ou plus récent), qui doit rester allumée et connectée. Texte uniquement, et seulement les SMS reçus après la connexion.

  4. L'email lit la boîte de réception

    Boîtes IMAP uniquement pour l'instant (Gmail avec un mot de passe d'application, votre domaine, la plupart des hébergeurs) ; pas encore d'Outlook ni de Microsoft 365. Le courrier classé en spam ou dans d'autres dossiers n'est pas vu.

  5. Tous les champs n'existent pas sur tous les canaux

    Le SMS n'a ni identifiant de conversation ni accusé de lecture, l'email n'a pas de réactions, WhatsApp n'a pas d'objet. Codez sur les champs communs et traitez le reste comme optionnel.

FAQ

Questions sur le webhook multicanal

Ilian, ingénieur logiciel senior

Écrit par

· Senior Software Engineer

Ingénieur logiciel senior, il fonde et dirige plusieurs sociétés tech. Il conçoit des produits autour des API de messagerie, de l’automatisation et de l’intégration de l’IA, et partage ici ce qu’il apprend en construisant.

Voir le profil LinkedInMis à jour le

Trois canaux, un seul handler.

Créez un compte, reliez votre WhatsApp, votre boîte mail et votre téléphone Android, et activez les webhooks avec Medium ou Max. L'envoi reste gratuit sur toutes les offres.