Receive messages with webhooks
Build a receiver that gets every customer message as it arrives, verifies it, deduplicates it and answers in time.
This guide builds an endpoint that receives message.received
every time a customer writes to you on any channel, and logs who wrote what. You need the
Business plan and an Owner or Admin login.
1. Write the receiver
The receiver must do four things, in this order: verify the signature against the raw
body, skip an event id it has already handled, answer 2xx within 10 seconds, then do the
work.
// A RevoplyAI webhook receiver on Express 4 or 5.
//
// npm install express
// REVOPLY_WEBHOOK_SECRET=whsec_… node express.mjs
import express from 'express';
import { verifyRevoplySignature } from '../verify.node.mjs';
const secret = process.env.REVOPLY_WEBHOOK_SECRET;
const app = express();
// Stands in for your database: remember the event ids you have handled.
const handled = new Set();
// #region route
// express.raw() on this route, registered before any app.use(express.json()): the signature
// covers the exact bytes we sent, and a parsed-then-re-serialised body is not those bytes.
app.post('/webhooks/revoply', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyRevoplySignature(req.body, req.get('X-Revoply-Signature'), secret)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
// The same event can arrive more than once: acknowledge a repeat and do nothing.
if (handled.has(event.id)) return res.sendStatus(200);
handled.add(event.id);
// Answer within 10 seconds, then do the work.
res.sendStatus(200);
setImmediate(() => handleEvent(event));
});
// #endregion
// #region handle
function handleEvent(event) {
// "Send test" posts sample data about nobody real, marked "test": true.
if (event.test) {
console.log(`test ${event.type} received`);
return;
}
switch (event.type) {
case 'message.received': {
const { conversation, contact, message } = event.data;
console.log(
`${contact?.name ?? contact?.phone ?? 'A visitor'} wrote on ${conversation.channelType}:`,
);
console.log(message.text ?? `[${message.type}]`);
break;
}
default:
// A type you did not expect is still acknowledged; new events may be added.
console.log(`ignored ${event.type}`);
}
}
// #endregion
// The rest of the app may parse JSON as usual, registered after the webhook route.
app.use(express.json());
app.listen(3000, () => console.log('Listening on port 3000, path /webhooks/revoply'));
Each one imports the verifier from Signatures. The id set is a stand-in: in production, record handled ids in your database, in the same transaction as the work they trigger.
2. Make it reachable
We deliver only to public https:// addresses. Deploy the receiver, or expose it from your
computer with a tunnel such as ngrok http 3000; see
Testing safely.
3. Add the endpoint
- Open Integrations → Webhooks → Add endpoint.
- Enter your receiver's URL, such as
https://example.com/webhooks/revoply, and choose Message received. - Save, and copy the signing secret it shows once into your receiver's configuration as
REVOPLY_WEBHOOK_SECRET.
4. Send a test
Choose Send test on the endpoint, pick Message received, and send. Your receiver
logs test message.received received, and the dashboard shows the 200 it answered.
The example is marked "test": true; the receiver above skips such events before doing
any work.
If the test fails, the dashboard shows why; a 400 usually means the signature check
failed, see Verify webhook signatures.
5. Read the message
Real events carry "test": false. The fields you will use most:
| Field | Holds |
|---|---|
data.conversation.id | The conversation; opaque, store it as it is |
data.conversation.channelType | whatsapp, instagram, web_widget… treat unknown values as "other" |
data.contact.name | The customer's name, when known, or null |
data.contact.phone | E.164 or null; see Identifiers |
data.message.type | text, image, audio, document… |
data.message.text | The text, or a media message's caption; at most 4,096 characters, or null |
The event fires once per customer message, on every channel, whoever is handling the conversation. It does not fire for messages your business sends: the assistant's replies, flows, or your team's.
Before going live
- Keep the secret in your server's configuration, never in the code.
- Record handled event ids durably, for at least as long as a delivery can be retried or resent (30 days covers both).
- Move slow work (CRM calls, emails) to a queue so every answer is fast.
- Watch the endpoint's delivery log for failures after deploys. An endpoint that fails 100 times in a row, or for 3 days, is switched off.