Forward inbound messages
Receive every customer message from WhatsApp, email, web chat and Meta channels at your own endpoint, signed and in real time.
The most common integration: every time a customer writes to you on any connected channel, your own system hears about it. Route it to the right team, log it in your CRM, trigger a workflow, or hand it to your own bot.
1. Subscribe
Create an endpoint in Developers → Webhooks, or with
POST /v2/webhook-endpoints, subscribed to
message.received. Add message.status_updated too if you send messages and want to know when
they are delivered.
The endpoint receives the conversations its owner can see: create it as (or with a key of) a
member who sees every conversation if you want them all. Over the API, the key also needs
messages:read to subscribe to message events.
2. Receive
{
"id": "evt_6b1c4e3d8a5f0c9b2d4e6f81",
"event": "message.received",
"livemode": true,
"workspaceId": "a3b5c7d9-1e2f-4a6b-8c0d-2e4f6a8b0c1d",
"entity": "message",
"entityId": "7d2e9f4a-6b1c-4e3d-8a5f-0c9b2d4e6f81",
"occurredAt": "2026-10-21T10:02:37.118Z",
"payload": {
"messageId": "7d2e9f4a-6b1c-4e3d-8a5f-0c9b2d4e6f81",
"conversationId": "c41e7b2a-5d6f-4e8a-9b1c-3d5e7f9a1b2c",
"connection": { "id": "9b2f6c1e-3d4a-4e5b-8f70-1a2b3c4d5e6f", "channel": "whatsapp", "kind": "whatsapp_cloud" },
"contact": {
"id": "e8f1a3c5-7b9d-4f2a-8c4e-6a8b0d2f4a6c",
"name": "Layla Haddad",
"identities": [{ "channel": "whatsapp_cloud", "externalId": "447700900123" }]
},
"kind": "image",
"text": "Hi, can I move my appointment to Thursday? Here is my booking.",
"media": [
{
"index": 0,
"url": "/v2/messages/7d2e9f4a-6b1c-4e3d-8a5f-0c9b2d4e6f81/attachments/0",
"mediaId": null,
"contentType": "image/jpeg",
"size": 183402,
"filename": null,
"available": true
}
],
"reply": null,
"quotedMessageId": null,
"app": null,
"receivedAt": "2026-10-21T10:02:37.118Z"
}
}The payload is a snapshot with the ids you need, the text, and the customer's handles. For the full contact or the conversation history, call the API with those ids.
Files. To fetch an attachment, GET its url with a key that has messages:read
(https://app.gauge.chat/backend + url). The answer's download.url is a signed link valid for
5 minutes: fetch the bytes from it right away rather than storing it. available: false means
Gauge holds no copy (url answers 404). See
Download a message's file.
Long messages. A body over 64 KB arrives with text cut to 4,096 characters,
"truncated": true and "fetchUrl": "/v2/messages/{id}". Fetch the full message from there.
3. Handle it safely
import express from 'express';
import { verifyGaugeSignature } from './verify-gauge-signature.js';
import { seenBefore, enqueue } from './store.js';
const app = express();
app.post('/gauge/webhooks', express.raw({ type: 'application/json' }), async (req, res) => {
// 1. Is it from Gauge? Check the raw bytes.
if (!verifyGaugeSignature(req.headers, req.body, process.env.GAUGE_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// 2. Already handled? Retries and replays reuse the event id.
if (await seenBefore(event.id)) return res.sendStatus(204);
// 3. Hand it off and answer at once. Gauge waits at most 10 seconds.
await enqueue(event);
res.sendStatus(204);
});
app.listen(3000);Then, in your worker:
- Skip test events (
event.test === true) and, in production, sandbox events (event.livemode === false). - Order by
occurredAtwithin a conversation: deliveries can arrive out of order. - Do not reply from inside the webhook handler. Reply from your worker with
POST /v2/messagesand anIdempotency-Keyderived from the inbound event (reply-to-${event.id}), so a retried event never produces a second reply.
Avoid loops
message.received fires for messages from customers. Your own replies, including those sent
through the API, raise message.sent, not message.received, so a bot that answers every
message.received does not answer itself.
What arrives, per channel
| Channel | Arrives as message.received |
|---|---|
| WhatsApp Cloud API, WhatsApp Web | Yes: text, media, locations, contacts, and button or list replies (reply) |
| Yes: one event per inbound email | |
| Web chat | Yes: each visitor message |
| Instagram, Messenger | Yes: direct messages |
| SMS | Later: sms.received, with SMS over the API |