# Unified messaging webhook: WhatsApp, email and SMS in one?

> Every WhatsApp, email and SMS reply in one signed JSON webhook, from your own number, mailbox and phone. 1 handler instead of 3 formats. Payload and code.

Source: https://unlimitedmessaging.app/articles/one-webhook-whatsapp-email-sms

Unified messaging webhook

# Every reply, on every channel,  
_in one webhook._

WhatsApp from your number, email from your mailbox, SMS from your phone: one endpoint receives them all in the same signed JSON envelope. One handler feeds your inbox, your AI agent or your CRM.

[Create a free account](https://app.unlimitedmessaging.app)[See paid plans](https://unlimitedmessaging.app/pricing)

The short answer

## Can one webhook receive WhatsApp, email and SMS?

Yes. Unlimited Messaging posts every incoming message from your linked WhatsApp number, your connected mailbox and your Android phone's SIM to one URL, as the same event, message.received, signed per [Standard Webhooks](https://www.standardwebhooks.com). The envelope never changes; data.channel.type says WHATSAPP, EMAIL or SMS. Webhooks are on the paid Medium and Max plans.

Assembled from separate providers, the same inbox means three integrations. Meta's Cloud API posts nested JSON signed with X-Hub-Signature-256 after a hub.challenge handshake. Twilio posts SMS and WhatsApp as form-encoded parameters signed with HMAC-SHA1\. SendGrid Inbound Parse posts multipart/form-data for a hostname whose MX you moved. Three parsers, three signature checks, three ideas of what a conversation is.

Here the channels are services beside one API, and that API announces each message the same way: id, direction, content, sender, sentAt, the conversation (a WhatsApp chat or group, an email thread root, an SMS number) and the account it came in on. Email adds subject, html and attachments; WhatsApp adds media, groups, reactions and edits. Delivery rules are shared too: answer 2xx within 10 seconds, 8 attempts over a little more than 3 days, deduplicate on eventId.

Replies close the loop from the same identities. replyToMessageId answers a WhatsApp chat or an email thread in place; an SMS answer names the SIM. Customers see your number and your address, never a provider's.

Related: [send email and WhatsApp in one API request](https://unlimitedmessaging.app/articles/email-whatsapp-single-api-request), and [keep sales on WhatsApp and email in one inbox](https://unlimitedmessaging.app/articles/sales-whatsapp-email-one-inbox).

![Illustration: two servers exchanging webhook events.](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Fone-webhook-whatsapp-email-sms-en.png&w=3840&q=75)

Definition
Unified messaging webhook

A single HTTP endpoint that receives inbound messages from several channels (WhatsApp, email, SMS) in one payload format, with a field naming the channel, so one handler serves every conversation.

Also searched as multichannel webhook, omnichannel webhook or omnichannel inbox API.

[Source: Standard Webhooks specification↗](https://www.standardwebhooks.com)

The events

## What does the unified webhook payload look like?

The same message.received event on every channel. What changes is data.channel.type and a few channel-only fields.

1. 01  
### Shared on every channel  
type, eventId, occurredAt; data.message with id, externalId, direction, fromMe, content, sentAt, receivedAt, sender, senderId; data.channel with type, account (id, name) and conversation (id, isGroup, address).
2. 02  
### WHATSAPP  
Media (mediaUrl, mediaType, mimeType), locations, quoted replies (replyTo), groups (participant, conversation.participants), plus message.reaction, message.edited and message.deleted.
3. 03  
### EMAIL  
subject, html (untrusted, sanitise it), attachments (up to 20 per email), conversation.id set to the thread root and replyTo to the message answered.
4. 04  
### SMS  
Text only. conversation.id is null, so thread on sender. Messages received after the Android app was signed in.
5. 05  
### Around the messages  
message.sent and message.status follow what you send on all three channels; account.status\_changed reports a number, mailbox or SIM that dropped.

Three channels, one envelopeJSON

```
// 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": "447700900123@s.whatsapp.net", "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": "claire@example.com", "subject": "Re: Quote #1042",
                 "html": "<p>Signed quote attached.</p>", "attachments": [ { "filename": "quote.pdf" } ] },
    "channel": { "type": "EMAIL", "account": { "id": "acc_8b2e...", "name": "sales@yourcompany.example" },
                 "conversation": { "id": "quote-1042.20261108@yourcompany.example", "isGroup": false,
                                   "address": "claire@example.com" } } } }

// 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" } } } }
```

Trimmed message.received bodies from the published schema, one per channel. The full event carries every field listed on the left.

Set it up

## How do you build one handler for every channel?

Link the three channels once, register one endpoint, and branch only where a channel really differs.

1. 01  
### Link your channels  
Scan a QR code for WhatsApp, connect your mailbox over IMAP/SMTP, install the Android app for SMS. Each one becomes an account.
2. 02  
### Register one endpoint  
POST /webhooks with your URL and eventTypes, or add it on the Webhooks page of the dashboard. Without accountIds it covers every account, even future ones.
3. 03  
### Verify once, branch late  
One signature check, one upsert on eventId, one inbox row. Only the reply call differs: replyToMessageId for WhatsApp and email, the SIM for 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.
```

There is no "every event" option: list the types you want. A type added later is never turned on for existing endpoints; PATCH /webhooks/{id} with the full list to add one.

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 and draftReply are yours (a table, an LLM call). The signature check follows the documented Standard Webhooks scheme, the same for every channel.

Vibe coding

## Building with Cursor, Claude Code, Lovable or Bolt? Paste this

Enough context for your AI editor to build the whole unified inbox route in one pass.

Prompt to copy

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"].

What people build

## What can you build with one webhook for every channel?

Three projects that are three integrations elsewhere and one handler here.

### A support inbox across channels

One list of conversations, a badge per channel, replies sent from your number or your address in the same thread. Add message.status to show delivered and read ticks where the channel reports them.

### An AI auto-responder

Feed content to Claude or GPT with the conversation history, send the answer back on the channel it came from, hand over to a human on keywords or low confidence.

### CRM sync

Attach every message to the contact by phone or email address, whatever the channel, with sentAt for the timeline.

1. 01  
### Channel fallback  
No reply on WhatsApp after a day? Follow up by email or SMS; the same POST /message can send on up to three channels at once.
2. 02  
### Ops monitoring  
account.status\_changed on all three channels tells you when a number, a mailbox or a phone stops relaying.
3. 03  
### No-code flows  
Point the webhook at n8n, Make or Zapier and switch on data.channel.type.

The table

## One inbox, three ways: Twilio, a DIY stack, or Unlimited Messaging

What it takes to receive WhatsApp, email and SMS replies in the same code.

__One inbox, three ways: Twilio, a DIY stack, or Unlimited Messaging__
| [Twilio + SendGrid](https://www.twilio.com/docs/messaging/guides/webhook-request)One vendor, several products | [Meta + parse service + SMS provider](https://developers.facebook.com/docs/graph-api/webhooks/getting-started)Do it yourself | Unlimited MessagingOne webhook, paid plans                            |                                                    |
| ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------- |
| Payload formats                                                                                               | Form-encoded for SMS and WhatsApp, multipart for email                                                                       | Meta JSON, provider format, multipart or JSON for email               | One JSON envelope                                  |
| Signature checks                                                                                              | X-Twilio-Signature (HMAC-SHA1), plus the parse endpoint's own protection                                                     | X-Hub-Signature-256 for Meta, one per other provider                  | One Standard Webhooks check                        |
| Identities customers see                                                                                      | Numbers leased from Twilio, a registered WhatsApp sender                                                                     | A number moved to the Cloud API, a rented SMS number                  | Your own number, mailbox and SIM                   |
| Setup before the first message                                                                                | Number purchase, WhatsApp sender registration, MX record                                                                     | Meta app and business portfolio, MX record, number rental             | QR scan, mailbox password, Android app             |
| Per-message fees                                                                                              | $0.0083 per US inbound SMS segment, $0.005 per WhatsApp message plus Meta fees                                               | Meta fees past 1,000 service replies per number a month, per-SMS fees | None; webhooks need Medium or Max                  |
| Official status                                                                                               | Official WhatsApp Business Platform                                                                                          | Official WhatsApp Business Platform                                   | WhatsApp via the unofficial linked-device protocol |

Checked on 9 November 2026 on Twilio's webhook, SMS and WhatsApp pricing pages, Meta's webhook and pricing docs and SendGrid's Inbound Parse guide (links in the sources).

The catch

## What are the limits of a unified webhook on your own channels?

One format removes the glue code. Each channel keeps its own nature.

1. 01  
### Webhooks are a paid feature  
Sending on all three channels is free on every plan, and incoming messages show in the web app inbox. Pushing them to your server needs Medium (€19 a month, or €17 a month billed yearly) or Max.
2. 02  
### WhatsApp is a linked device  
The number is linked like WhatsApp Web, through an unofficial protocol: spam gets it restricted, and WhatsApp logs out linked devices when the main phone stays offline 14 days.
3. 03  
### SMS needs an Android phone  
The SIM relays through the free Android app (8.0 or newer), which must stay on and online. Text only, and only texts received after sign-in.
4. 04  
### Email reads the inbox  
IMAP mailboxes only for now (Gmail with an app password, own domain, most hosts); no Outlook or Microsoft 365 yet. Mail filed in spam or other folders is not seen.
5. 05  
### Not every field exists on every channel  
SMS has no conversation id and no read receipts, email has no reactions, WhatsApp has no subject. Code against the shared fields and treat the rest as optional.

How to choose

### Unified webhook or separate providers?

You need official WhatsApp branding, template campaigns at scale, short codes or thousands of generated email addresses: stitch official providers together and budget for each. You want the replies customers already send to your number, your address and your phone in one place, with one handler and answers from the same identities: link the three and subscribe one endpoint to message.received.

FAQ

## Questions about a unified messaging webhook

Yes. With Unlimited Messaging, one endpoint subscribed to message.received gets incoming messages from your linked WhatsApp number, your mailbox and your Android SIM in the same signed JSON envelope; data.channel.type says which. Webhooks require the Medium or Max plan.

data.channel.type is WHATSAPP, EMAIL or SMS, and data.channel.account names the number, mailbox or SIM the message came in on. Email adds subject, html and attachments.

Use data.channel.conversation.id: the WhatsApp chat or group, or the root Message-ID of an email thread. SMS has no conversation id, so group on sender. To merge one person across channels, match their phone number and email address in your CRM.

Signed per Standard Webhooks (HMAC-SHA256 over id.timestamp.rawBody, whsec\_ secret). Retried on 429, 5xx, timeouts and network errors after 30 s, 2 min, 10 min, 1 h, 6 h, 1 day and 2 days: 8 attempts over a little more than 3 days.

Yes. Pass the content to your model and send the answer with POST /message: replyToMessageId for WhatsApp and email (same chat, same thread), or to.phone with an SMS channel entry and the SIM's accountId.

Neither. One endpoint without accountIds covers every account you have, including ones linked later. Add accountIds when a team should only see some of them.

Yes: message.received for incoming, message.sent for what you send through the API, and message.self\_sent for what you type on your WhatsApp phone, so the inbox shows both sides.

No. Unlimited Messaging charges no per-message fee on any channel; webhooks come with the Medium or Max plan. SMS replies count against your own carrier plan.

Sources

## Sources & references

To check the information in this article for yourself:

1. [01Standard Webhooks specification↗standardwebhooks.com](https://www.standardwebhooks.com)
2. [02Getting started with webhooks (hub.challenge, X-Hub-Signature-256)↗Meta for Developers](https://developers.facebook.com/docs/graph-api/webhooks/getting-started)
3. [03WhatsApp Business Platform pricing↗Meta for Developers](https://developers.facebook.com/docs/whatsapp/pricing)
4. [04Incoming message webhook request (form-encoded)↗Twilio Docs](https://www.twilio.com/docs/messaging/guides/webhook-request)
5. [05Webhooks security (X-Twilio-Signature, HMAC-SHA1)↗Twilio Docs](https://www.twilio.com/docs/usage/webhooks/webhooks-security)
6. [06SMS pricing in the United States↗Twilio](https://www.twilio.com/en-us/sms/pricing/us)
7. [07WhatsApp pricing ($0.005 per message, inbound or outbound)↗Twilio](https://www.twilio.com/en-us/whatsapp/pricing)
8. [08Setting up the Inbound Parse webhook (MX, multipart/form-data)↗Twilio SendGrid Docs](https://www.twilio.com/docs/sendgrid/for-developers/parsing-email/setting-up-the-inbound-parse-webhook)
9. [09About linked devices (logout after 14 days without the main phone)↗WhatsApp Help Center](https://faq.whatsapp.com/378279804439436)
10. [10Unlimited Messaging API reference - Webhooks↗Unlimited Messaging](https://docs.unlimitedmessaging.app)

![Ilian, senior software engineer](https://ilianazz.com/images/iliazz.png)

Written by

[Ilian](https://unlimitedmessaging.app/authors/ilian) · Senior Software Engineer

A senior software engineer, he founds and runs several tech companies. He builds products around messaging APIs, automation and AI integration, and shares here what he learns while building.

[View LinkedIn profile↗](https://www.linkedin.com/in/ilianAzz/)Updated November 9, 2026

![](https://unlimitedmessaging.app/assets/logos/whatsapp.svg)![](https://unlimitedmessaging.app/assets/logos/email.svg)![](https://unlimitedmessaging.app/assets/logos/sms.svg)

## Three channels, one handler.

Create an account, link your WhatsApp, mailbox and Android phone, and turn on webhooks with Medium or Max. Sending stays free on every plan.

[Create a free account→](https://app.unlimitedmessaging.app)

Similar articles
* [![](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Fwhatsapp-webhook-incoming-messages-en.png&w=3840&q=75)WhatsApp webhook without Meta approval: is it possible?Incoming WhatsApp messages from your own number, pushed to your server as signed JSON. Events, payload, Node.js handler.](https://unlimitedmessaging.app/articles/whatsapp-webhook-incoming-messages)
* [![](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Femail-inbound-webhook-en.png&w=3840&q=75)Inbound email webhook without changing your MX: how?Your own mailbox to a webhook: subject, HTML, attachments and thread ids as signed JSON, with replies from the same address.](https://unlimitedmessaging.app/articles/email-inbound-webhook)
* [![](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Fsms-webhook-incoming-sms-en.png&w=3840&q=75)SMS webhook on your own number: do you need Twilio?Receive the SMS your own SIM gets as signed JSON, through a free Android app. No rented number, no fee per incoming text.](https://unlimitedmessaging.app/articles/sms-webhook-incoming-sms)
