Webhooks

Forward inbound messages

Receive every customer message from WhatsApp, email, web chat and Meta channels at your own endpoint, signed and in real time.

Live

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

inbound.js
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 occurredAt within a conversation: deliveries can arrive out of order.
  • Do not reply from inside the webhook handler. Reply from your worker with POST /v2/messages and an Idempotency-Key derived 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

ChannelArrives as message.received
WhatsApp Cloud API, WhatsApp WebYes: text, media, locations, contacts, and button or list replies (reply)
EmailYes: one event per inbound email
Web chatYes: each visitor message
Instagram, MessengerYes: direct messages
SMSLater: sms.received, with SMS over the API

On this page