WhatsApp Webhook Setup: Real-Time Message Processing
Real-time message processing is the backbone of any interactive WhatsApp integration. Whether you're building a chatbot, syncing messages to a CRM, or tracking delivery receipts, you need to receive events as they happen. WSAPI offers two real-time delivery mechanisms: HTTP webhooks and Server-Sent Events (SSE). This guide covers both.
Webhooks vs SSE: Which to Choose
| Feature | Webhooks | SSE |
|---|---|---|
| Direction | WSAPI pushes to you | You connect to WSAPI |
| Connection | Per-event HTTP POST | Persistent stream |
| Reliability | Auto-retry on failure | Reconnect on drop |
| Firewall | Needs public endpoint | Outbound only |
| Best for | Servers, production | Local dev, debugging |
Use webhooks for production. Use SSE for local development or when you can't expose a public endpoint. You can use both simultaneously.
Setting Up Webhooks
Step 1: Configure Your Webhook URL
Point your WSAPI instance to your webhook endpoint via the dashboard or API:
curl -X PUT https://api.wsapi.chat/instances/{instanceId}/webhook \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/whatsapp",
"events": ["message", "message.ack", "presence.update", "group.join"]
}'Step 2: Build a Webhook Receiver (Node.js)
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Middleware: verify signature
function verifySignature(req, res, next) {
const signature = req.headers['x-signature'];
if (!signature || !WEBHOOK_SECRET) return next();
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expected) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
app.post('/webhooks/whatsapp', verifySignature, (req, res) => {
const { event, data } = req.body;
switch (event) {
case 'message':
console.log(`[${data.fromMe ? 'SENT' : 'RECV'}] ${data.from}: ${data.body}`);
break;
case 'message.ack':
const status = ['ERROR', 'PENDING', 'SERVER', 'DELIVERED', 'READ', 'PLAYED'];
console.log(`[ACK] Message ${data.id}: ${status[data.ack] || data.ack}`);
break;
case 'presence.update':
console.log(`[PRESENCE] ${data.id}: ${data.status}`);
break;
case 'group.join':
console.log(`[GROUP] ${data.participant} joined ${data.groupId}`);
break;
}
res.sendStatus(200);
});
app.listen(3000, () => console.log('Webhook server running on port 3000'));Step 3: Build a Webhook Receiver (Python)
import hmac
import hashlib
import json
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
WEBHOOK_SECRET = "your-webhook-secret"
async def verify_signature(request: Request):
signature = request.headers.get("x-signature")
if not signature or not WEBHOOK_SECRET:
return
body = await request.body()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()
if signature != expected:
raise HTTPException(status_code=401, detail="Invalid signature")
@app.post("/webhooks/whatsapp")
async def webhook(request: Request):
await verify_signature(request)
payload = await request.json()
event = payload["event"]
data = payload["data"]
if event == "message":
direction = "SENT" if data.get("fromMe") else "RECV"
print(f"[{direction}] {data['from']}: {data.get('body', '[media]')}")
elif event == "message.ack":
statuses = ["ERROR", "PENDING", "SERVER", "DELIVERED", "READ", "PLAYED"]
ack = data.get("ack", 0)
print(f"[ACK] Message {data['id']}: {statuses[ack] if ack < len(statuses) else ack}")
return {"status": "ok"}Event Types Deep Dive
WSAPI delivers events in four categories. Subscribe only to the events you need to reduce noise and bandwidth:
Message Events
message— New incoming or outgoing message (text, media, location, contact, etc.)message.revoke— A message was deleted by the sender
Delivery Events
message.ack— Delivery status update: 0=error, 1=pending, 2=server, 3=delivered, 4=read, 5=played
Presence Events
presence.update— Contact is online, offline, typing, or recording audio
Group Events
group.join— A participant joined (or was added to) a groupgroup.leave— A participant left (or was removed from) a groupgroup.update— Group metadata changed (name, description, picture)
SSE: Server-Sent Events
SSE is perfect for local development or environments where you can't expose a public webhook URL. Connect to the WSAPI event stream and receive events over a persistent HTTP connection:
// Browser or Node.js with eventsource package
const eventSource = new EventSource(
'https://api.wsapi.chat/events/stream?token=your-api-key&instanceId=your-instance-id'
);
eventSource.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log('New message:', data.from, data.body);
});
eventSource.addEventListener('message.ack', (e) => {
const data = JSON.parse(e.data);
console.log('Delivery update:', data.id, data.ack);
});
eventSource.addEventListener('presence.update', (e) => {
const data = JSON.parse(e.data);
console.log('Presence:', data.id, data.status);
});
eventSource.onerror = () => {
console.log('Connection lost, reconnecting...');
};import requests
import json
url = "https://api.wsapi.chat/events/stream"
params = {"token": "your-api-key", "instanceId": "your-instance-id"}
with requests.get(url, params=params, stream=True) as response:
for line in response.iter_lines():
if not line:
continue
decoded = line.decode("utf-8")
if decoded.startswith("data: "):
payload = json.loads(decoded[6:])
print(f"[{payload['event']}] {json.dumps(payload['data'], indent=2)}")Production Best Practices
- Always verify signatures — Use the
x-signatureHMAC-SHA256 header to validate that webhook payloads originate from WSAPI. - Respond quickly — Return a 200 status within 5 seconds. Do heavy processing asynchronously (queue the event, process later).
- Handle retries idempotently — WSAPI retries failed deliveries. Use the event ID to deduplicate if your handler isn't idempotent.
- Use HTTPS — Always use HTTPS for your webhook endpoint. WSAPI will not deliver to plain HTTP in production.
- Monitor delivery — Track webhook delivery success rates in your logging. The WSAPI dashboard also shows delivery stats.
- Filter events — Only subscribe to events you actually process. Fewer events = less load, lower latency.
Next Steps
- Build a chatbot that uses webhooks for real-time conversations
- Send messages with Python and connect to your webhook receiver
- Read the full API docs for webhook configuration options
- Get started — connect your first WhatsApp account and configure webhooks