Skip to main content

Overview

The webhook system sends HTTP POST requests to configured URLs whenever WhatsApp events occur. Each webhook request includes event data in JSON format and security headers for verification.

Configuration

Environment Variables

# Single webhook URL
WHATSAPP_WEBHOOK=https://yourapp.com/webhook

# Multiple webhook URLs (comma-separated)
WHATSAPP_WEBHOOK=https://app1.com/webhook,https://app2.com/webhook

# Webhook secret for HMAC verification
WHATSAPP_WEBHOOK_SECRET=your-super-secret-key

Command Line Flags

# Single webhook
./whatsapp rest --webhook="https://yourapp.com/webhook"

# Multiple webhooks
./whatsapp rest --webhook="https://app1.com/webhook,https://app2.com/webhook"

# Custom secret
./whatsapp rest --webhook-secret="your-secret-key"

Event Filtering

You can configure which events are forwarded to your webhook using the WHATSAPP_WEBHOOK_EVENTS environment variable or --webhook-events CLI flag.

Available Events

EventDescription
messageText, media, contact, location, and other message types
message.reactionEmoji reactions to messages
message.revokedDeleted/revoked messages
message.editedEdited messages
message.ackDelivery and read receipts
message.deletedMessages deleted for the user
group.participantsGroup member join/leave/promote/demote events
group.joinedYou were added to a group
newsletter.joinedYou subscribed to a newsletter/channel
newsletter.leftYou unsubscribed from a newsletter
newsletter.messageNew message(s) posted in a newsletter
newsletter.muteNewsletter mute setting changed
call.offerIncoming call received

Configuration Examples

# Only receive message and read receipt events
WHATSAPP_WEBHOOK_EVENTS=message,message.ack

# Receive all message-related events
WHATSAPP_WEBHOOK_EVENTS=message,message.reaction,message.revoked,message.edited,message.ack,message.deleted

# Receive only group events
WHATSAPP_WEBHOOK_EVENTS=group.participants

# Receive newsletter events
WHATSAPP_WEBHOOK_EVENTS=newsletter.joined,newsletter.left,newsletter.message,newsletter.mute
If WHATSAPP_WEBHOOK_EVENTS is empty or not set, all events are forwarded (default behavior).

Security

HMAC Signature Verification

All webhook requests include an HMAC SHA256 signature for security verification:
  • Header: X-Hub-Signature-256
  • Format: sha256={signature}
  • Algorithm: HMAC SHA256
  • Default Secret: secret (configurable via --webhook-secret or WHATSAPP_WEBHOOK_SECRET)

Verification Examples

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload, 'utf8')
        .digest('hex');

    const receivedSignature = signature.replace('sha256=', '');
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature, 'hex'),
        Buffer.from(receivedSignature, 'hex')
    );
}

TLS Configuration

If you encounter TLS certificate verification errors (e.g., with Cloudflare tunnels or self-signed certificates):
--webhook-insecure-skip-verify=true
# Or
WHATSAPP_WEBHOOK_INSECURE_SKIP_VERIFY=true
This option disables TLS certificate verification and should only be used in development/testing environments or with services like Cloudflare tunnels. For production, use proper SSL certificates (e.g., Let’s Encrypt).

Payload Structure

All webhook payloads follow a consistent top-level structure:
{
  "event": "message",
  "device_id": "[email protected]",
  "payload": {
    // Event-specific fields
  }
}

Top-Level Fields

FieldTypeDescription
eventstringEvent type (see Available Events table above)
device_idstringJID of the device that received this event (e.g., [email protected])
payloadobjectEvent-specific payload data

Event Examples

Text Message

{
  "event": "message",
  "device_id": "[email protected]",
  "payload": {
    "id": "3EB0C127D7BACC83D6A1",
    "chat_id": "[email protected]",
    "from": "[email protected]",
    "from_lid": "251556368777322@lid",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T10:30:00Z",
    "is_from_me": false,
    "body": "Hello, how are you?"
  }
}

Image Message

{
  "event": "message",
  "device_id": "[email protected]",
  "payload": {
    "id": "3EB0C127D7BACC83D6A3",
    "chat_id": "[email protected]",
    "from": "[email protected]",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:05:51Z",
    "body": "Check this out!",
    "image": {
      "path": "statics/media/1752404751-ad9e37ac-c658-4fe5-8d25-ba4a3f4d58fd.jpeg",
      "caption": "Check this out!"
    }
  }
}

Message Reaction

{
  "event": "message.reaction",
  "device_id": "[email protected]",
  "payload": {
    "id": "88760C69D1F35FEB239102699AE9XXXX",
    "chat_id": "[email protected]",
    "from": "[email protected]",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T10:40:00Z",
    "is_from_me": false,
    "reaction": "👍",
    "reacted_message_id": "3EB0C127D7BACC83D6A1"
  }
}

Delivery Receipt

{
  "event": "message.ack",
  "device_id": "[email protected]",
  "timestamp": "2025-07-18T22:44:20Z",
  "payload": {
    "ids": [
      "3EB00106E8BE0F407E88EC"
    ],
    "chat_id": "[email protected]",
    "from": "[email protected]",
    "from_lid": "251556368777322@lid",
    "receipt_type": "delivered",
    "receipt_type_description": "means the message was delivered to the device (but the user might not have noticed)."
  }
}

Group Member Join

{
  "event": "group.participants",
  "device_id": "[email protected]",
  "timestamp": "2025-07-28T10:30:00Z",
  "payload": {
    "chat_id": "[email protected]",
    "type": "join",
    "jids": [
      "[email protected]",
      "[email protected]"
    ]
  }
}

Newsletter Joined

{
  "event": "newsletter.joined",
  "device_id": "[email protected]",
  "timestamp": "2026-01-18T12:00:00Z",
  "payload": {
    "newsletter_id": "120363123456789@newsletter",
    "name": "Tech News Daily",
    "description": "Latest tech updates and news"
  }
}

Incoming Call

{
  "event": "call.offer",
  "device_id": "[email protected]",
  "timestamp": "2026-02-05T12:00:00Z",
  "payload": {
    "call_id": "ABC123DEF456",
    "from": "[email protected]",
    "auto_rejected": false,
    "remote_platform": "android",
    "remote_version": "2.24.1.5"
  }
}
You can auto-reject incoming calls using WHATSAPP_AUTO_REJECT_CALL=true or --auto-reject-call=true.

Implementation Example

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.raw({type: 'application/json'}));

app.post('/webhook', (req, res) => {
    const signature = req.headers['x-hub-signature-256'];
    const payload = req.body;
    const secret = 'your-secret-key';

    // Verify signature
    if (!verifyWebhookSignature(payload, signature, secret)) {
        return res.status(401).send('Unauthorized');
    }

    // Parse and process webhook data
    const data = JSON.parse(payload);
    console.log('Received webhook:', data);

    // Handle different event types
    switch (data.event) {
        case 'message':
            console.log('New message:', {
                id: data.payload.id,
                from: data.payload.from,
                body: data.payload.body,
                chat_id: data.payload.chat_id
            });
            break;

        case 'message.reaction':
            console.log('Reaction:', {
                reaction: data.payload.reaction,
                reacted_message_id: data.payload.reacted_message_id
            });
            break;

        case 'message.ack':
            console.log(`Message ${data.payload.receipt_type}:`, {
                chat_id: data.payload.chat_id,
                message_ids: data.payload.ids
            });
            break;

        case 'group.participants':
            console.log(`Group ${data.payload.type} event:`, {
                chat_id: data.payload.chat_id,
                affected_users: data.payload.jids
            });
            break;

        case 'call.offer':
            console.log('Incoming call:', {
                call_id: data.payload.call_id,
                from: data.payload.from,
                auto_rejected: data.payload.auto_rejected
            });
            break;
    }

    res.status(200).send('OK');
});

function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload, 'utf8')
        .digest('hex');

    const receivedSignature = signature.replace('sha256=', '');
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature, 'hex'),
        Buffer.from(receivedSignature, 'hex')
    );
}

app.listen(3001, () => {
    console.log('Webhook server listening on port 3001');
});

Error Handling

The webhook system includes retry logic with exponential backoff:
  • Timeout: 10 seconds per request
  • Max Attempts: 5 retries
  • Backoff: Exponential (1s, 2s, 4s, 8s, 16s)

Best Practices

Always verify signatures

Ensure webhook authenticity by validating HMAC signatures

Handle duplicates

The same event might be sent multiple times due to retries

Process quickly

Respond within 10 seconds to avoid timeouts

Use HTTPS

Ensure secure transmission of webhook data

Troubleshooting

Webhook not receiving events

  • Check webhook URL is accessible from the internet
  • Verify webhook configuration in environment variables
  • Check firewall and network settings

Signature verification fails

  • Ensure webhook secret matches configuration
  • Use raw request body for signature calculation
  • Check HMAC implementation matches examples above

Timeouts

  • Optimize webhook processing speed
  • Implement asynchronous processing
  • Return response quickly, process in background

Debug Logging

Enable debug mode to see webhook logs:
./whatsapp rest --debug=true --webhook="https://yourapp.com/webhook"
This will show detailed logs of webhook delivery attempts and errors.

Build docs developers (and LLMs) love