cd /blog
GET/blog/whatsapp-webhook-setup-real-time-processing200OK
tutorialwebhooksnodejspython

WhatsApp Webhook Setup: Real-Time Message Processing

March 19, 2026|9 min read

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

FeatureWebhooksSSE
DirectionWSAPI pushes to youYou connect to WSAPI
ConnectionPer-event HTTP POSTPersistent stream
ReliabilityAuto-retry on failureReconnect on drop
FirewallNeeds public endpointOutbound only
Best forServers, productionLocal 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:

PUTconfigure_webhook.sh
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)

webhook.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)

webhook.py
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 group
  • group.leave — A participant left (or was removed from) a group
  • group.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:

sse_client.js
// 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...');
};
sse_client.py
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

  1. Always verify signatures — Use the x-signature HMAC-SHA256 header to validate that webhook payloads originate from WSAPI.
  2. Respond quickly — Return a 200 status within 5 seconds. Do heavy processing asynchronously (queue the event, process later).
  3. Handle retries idempotently — WSAPI retries failed deliveries. Use the event ID to deduplicate if your handler isn't idempotent.
  4. Use HTTPS — Always use HTTPS for your webhook endpoint. WSAPI will not deliver to plain HTTP in production.
  5. Monitor delivery — Track webhook delivery success rates in your logging. The WSAPI dashboard also shows delivery stats.
  6. Filter events — Only subscribe to events you actually process. Fewer events = less load, lower latency.

Next Steps