Webhook Setup
Receive real-time WhatsApp delivery receipts and inbound messages in your application
Webhook Setup
Webhooks let Chatbase notify your server instantly when messages are delivered, read, or when customers reply. This guide shows you how to set one up with Node.js / Express.
How It Works
Customer sends message
↓
WhatsApp / Meta
↓
Chatbase server
↓
POST to your webhook
↓
Your application processes event
Step 1 — Build Your Webhook Endpoint
Create a public HTTPS endpoint that accepts POST requests:
const express = require('express');
const app = express();
// Use raw body parser for signature verification
app.use(express.json());
app.post('/webhook/whatsapp', (req, res) => {
// Always respond 200 immediately
res.sendStatus(200);
const { event, ...payload } = req.body;
processEvent(event, payload);
});
function processEvent(event, payload) {
switch (event) {
case 'message':
console.log(`Inbound from ${payload.from}: ${payload.text?.body}`);
break;
case 'message_status':
console.log(`Message ${payload.messageId} is now: ${payload.status}`);
break;
case 'template_status':
console.log(`Template "${payload.templateName}" status: ${payload.status}`);
break;
}
}
app.listen(3000, () => console.log('Webhook server running on port 3000'));
Step 2 — Make It Public
During development, use a tunneling tool to expose your local server:
# Using ngrok
npx ngrok http 3000
# Output:
# Forwarding https://abc123.ngrok.io -> http://localhost:3000
Your webhook URL will be: https://abc123.ngrok.io/webhook/whatsapp
For production, deploy to a server with a real HTTPS domain.
Step 3 — Register in the Dashboard
- Go to CRM dashboard → Integrations → WhatsApp
- Find the Webhook URL field
- Enter your endpoint URL
- Save — Chatbase sends a verification challenge and your endpoint must respond correctly
Step 4 — Handle the Verification Challenge
When you register a webhook URL, Chatbase sends a GET request to verify ownership:
app.get('/webhook/whatsapp', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
if (mode === 'subscribe' && token === process.env.WEBHOOK_VERIFY_TOKEN) {
res.status(200).send(challenge); // Echo back the challenge
} else {
res.sendStatus(403);
}
});
Set WEBHOOK_VERIFY_TOKEN in your .env to any secret string. Enter the same value in the dashboard when registering your webhook.
Step 5 — Verify Request Signatures (Production)
In production, verify that incoming webhook requests genuinely come from Chatbase:
const crypto = require('crypto');
function verifySignature(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature ?? ''),
Buffer.from(expected)
);
}
// Use raw body middleware to get the unparsed body
app.post('/webhook/whatsapp',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['x-chatbase-signature'];
if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
res.sendStatus(200);
const payload = JSON.parse(req.body);
processEvent(payload.event, payload);
}
);
Full Event Payloads
Inbound Text Message
{
"event": "message",
"from": "919876543210",
"name": "Rahul Sharma",
"messageId": "wamid.HBgLOTE5ODc2NTQzMjEwFQ...",
"type": "text",
"text": { "body": "Hello, I need help with my order" },
"timestamp": "2024-06-10T09:30:00.000Z"
}
Inbound Image
{
"event": "message",
"from": "919876543210",
"type": "image",
"image": {
"id": "media-id-xxx",
"mimeType": "image/jpeg",
"caption": "Here is my receipt"
},
"timestamp": "2024-06-10T09:31:00.000Z"
}
Delivery Status Update
{
"event": "message_status",
"messageId": "wamid.HBgLOTE5ODc2NTQzMjEwFQ...",
"to": "919876543210",
"status": "read",
"timestamp": "2024-06-10T09:30:10.000Z"
}
Status progression: sent → delivered → read (or failed)
Auto-Reply Example
Reply automatically when a customer messages you:
async function processEvent(event, payload) {
if (event !== 'message') return;
const { from, text } = payload;
const body = text?.body?.toLowerCase() ?? '';
let reply = 'Thanks for your message! Our team will respond shortly.';
if (body.includes('price') || body.includes('cost')) {
reply = 'Check our pricing at https://chatbase.in/pricing';
} else if (body.includes('help')) {
reply = 'I can help! Reply with: ORDER, TRACK, RETURN, or SUPPORT';
}
await api.post('/messages/send', {
to: from,
type: 'text',
text: { body: reply },
});
}
Retry Policy
If your server is unavailable or returns a non-200 status:
| Attempt | Delay |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 5 minutes |
| 4th retry | 30 minutes |
Design your webhook handler to be idempotent — the same event may be delivered more than once.
Checklist
- Endpoint responds
200 OKimmediately (process async) - Verification challenge handler on
GETsame path - Signature verification in production
- Idempotent event handling (deduplicate by
messageId) - Retry logic for downstream calls
- Logging for debugging
Next Steps
- Webhooks Reference — Full event type reference
- Send Message API — Reply to inbound messages