WhatsApp Webhooks Integration Guide for Developers

Building a WhatsApp integration? This developer guide explains how to set up and use webhooks to receive real-time updates from WhatsApp Business API.

What Are Webhooks?

Definition

Webhooks are HTTP callbacks that notify your server when events happen in WhatsApp, such as incoming messages, delivery status updates, or template status changes.

How It Works

Event Flow:

  • User sends message to your WhatsApp
  • WhatsApp sends HTTP POST to your webhook
  • Your server receives the payload
  • Your server processes and responds
  • You send reply via API (if needed)
  • Setting Up Webhooks

    Prerequisites

    Requirements:
    
    

    ✅ Publicly accessible HTTPS endpoint ✅ Valid SSL certificate ✅ Server to process requests ✅ WhatsApp Business API access ✅ Verification token ready

    Endpoint Requirements

    Your webhook must:
    
    

    • Use HTTPS (TLS 1.2+) • Be publicly accessible • Respond within 20 seconds • Return 200 OK quickly • Handle GET (verification) • Handle POST (events)

    Verification Setup

    // Express.js verification endpoint
    app.get('/webhook', (req, res) => {
    const mode = req.query['hub.mode'];
    const token = req.query['hub.verify_token'];
    const challenge = req.query['hub.challenge'];
    
    

    // Your verification token const VERIFY_TOKEN = process.env.VERIFY_TOKEN;

    if (mode === 'subscribe' && token === VERIFY_TOKEN) { console.log('Webhook verified'); res.status(200).send(challenge); } else { res.sendStatus(403); } });

    Receiving Events

    // Express.js event handler
    app.post('/webhook', (req, res) => {
    // Acknowledge receipt immediately
    res.sendStatus(200);
    
    

    // Process asynchronously const body = req.body;

    if (body.object === 'whatsapp_business_account') { body.entry.forEach(entry => { entry.changes.forEach(change => { if (change.field === 'messages') { handleMessages(change.value); } }); }); } });

    Webhook Events

    Message Events

    // Incoming message structure
    {
    "object": "whatsapp_business_account",
    "entry": [{
    "id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
    "changes": [{
    "value": {
    "messaging_product": "whatsapp",
    "metadata": {
    "display_phone_number": "966501234567",
    "phone_number_id": "PHONE_NUMBER_ID"
    },
    "contacts": [{
    "profile": {
    "name": "Customer Name"
    },
    "wa_id": "966509876543"
    }],
    "messages": [{
    "from": "966509876543",
    "id": "wamid.xxx",
    "timestamp": "1705744200",
    "type": "text",
    "text": {
    "body": "Hello!"
    }
    }]
    },
    "field": "messages"
    }]
    }]
    }

    Message Types

    // Text message
    {
    "type": "text",
    "text": { "body": "Hello!" }
    }
    
    

    // Image message { "type": "image", "image": { "id": "MEDIA_ID", "mime_type": "image/jpeg", "sha256": "xxx", "caption": "Check this out" } }

    // Location message { "type": "location", "location": { "latitude": 24.7136, "longitude": 46.6753, "name": "Riyadh", "address": "Saudi Arabia" } }

    // Interactive reply { "type": "interactive", "interactive": { "type": "button_reply", "button_reply": { "id": "button_1", "title": "Track Order" } } }

    Status Updates

    // Delivery status
    {
    "statuses": [{
    "id": "wamid.xxx",
    "status": "delivered",
    "timestamp": "1705744300",
    "recipient_id": "966509876543",
    "conversation": {
    "id": "CONVERSATION_ID",
    "origin": {
    "type": "user_initiated"
    }
    },
    "pricing": {
    "billable": true,
    "pricing_model": "CBP",
    "category": "service"
    }
    }]
    }
    
    

    // Status types: // sent - Message sent to WhatsApp // delivered - Delivered to device // read - User read the message // failed - Delivery failed

    Processing Events

    Message Handler

    async function handleMessages(value) {
    const messages = value.messages || [];
    const contacts = value.contacts || [];
    
    

    for (const message of messages) { const from = message.from; const messageId = message.id; const timestamp = message.timestamp; const type = message.type;

    // Get sender name const contact = contacts.find(c => c.wa_id === from); const name = contact?.profile?.name || 'Customer';

    // Process by type switch (type) { case 'text': await handleTextMessage(from, message.text.body, name); break; case 'image': await handleImageMessage(from, message.image); break; case 'interactive': await handleInteractiveMessage(from, message.interactive); break; default: await handleUnknownMessage(from, type); } } }

    Status Handler

    async function handleStatuses(statuses) {
    for (const status of statuses) {
    const messageId = status.id;
    const statusType = status.status;
    const recipientId = status.recipient_id;
    
    

    // Update message status in database await updateMessageStatus(messageId, statusType);

    // Handle specific statuses if (statusType === 'failed') { const error = status.errors?.[0]; console.error(Message failed: ${error?.message}); await handleFailedMessage(messageId, error); }

    if (statusType === 'read') { await trackReadReceipt(messageId, recipientId); } } }

    Security

    Signature Verification

    const crypto = require('crypto');
    
    

    function verifySignature(req, secret) { const signature = req.headers['x-hub-signature-256']; if (!signature) return false;

    const expectedSignature = 'sha256=' + crypto .createHmac('sha256', secret) .update(JSON.stringify(req.body)) .digest('hex');

    return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); }

    // Use in middleware app.post('/webhook', (req, res, next) => { if (!verifySignature(req, process.env.APP_SECRET)) { return res.sendStatus(401); } next(); });

    Best Practices

    Security checklist:
    
    

    ✅ Verify webhook signatures ✅ Use HTTPS only ✅ Validate payload structure ✅ Rate limit endpoints ✅ Log all requests ✅ Handle errors gracefully ✅ Use environment variables ✅ Rotate secrets regularly

    Error Handling

    Common Errors

    // Error handling wrapper
    async function processWebhook(body) {
    try {
    // Process normally
    await handleWebhookBody(body);
    } catch (error) {
    // Log error
    console.error('Webhook error:', error);
    
    

    // Store for retry if needed await storeFailedWebhook(body, error);

    // Don't throw - still return 200 // to prevent WhatsApp retries } }

    Retry Logic

    // WhatsApp retries if no 200 response
    
    

    Retry schedule: 1st retry: 15 minutes 2nd retry: 30 minutes 3rd retry: 1 hour 4th retry: 3 hours 5th retry: 6 hours 6th retry: 12 hours 7th retry: 24 hours

    After 7 retries: Webhook disabled

    Solution: • Always return 200 quickly • Process asynchronously • Log failures for debugging

    Performance

    Async Processing

    // Don't block the response
    app.post('/webhook', async (req, res) => {
    // Respond immediately
    res.sendStatus(200);
    
    

    // Process in background setImmediate(async () => { try { await processWebhook(req.body); } catch (error) { console.error('Background processing error:', error); } }); });

    Queue Processing

    // Use a queue for reliability
    const Queue = require('bull');
    const webhookQueue = new Queue('webhooks');
    
    

    app.post('/webhook', (req, res) => { res.sendStatus(200);

    webhookQueue.add({ body: req.body, timestamp: Date.now() }); });

    webhookQueue.process(async (job) => { await processWebhook(job.data.body); });

    Testing

    Local Development

    # Use ngrok for local testing
    ngrok http 3000
    
    

    Output:

    https://abc123.ngrok.io -> http://localhost:3000

    Use this URL for webhook configuration

    Test Payloads

    // Send test webhook
    const testPayload = {
    object: "whatsapp_business_account",
    entry: [{
    id: "123456789",
    changes: [{
    value: {
    messaging_product: "whatsapp",
    messages: [{
    from: "966501234567",
    id: "test_message_id",
    timestamp: Date.now().toString(),
    type: "text",
    text: { body: "Test message" }
    }]
    },
    field: "messages"
    }]
    }]
    };
    
    

    // POST to your local server fetch('http://localhost:3000/webhook', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(testPayload) });

    Get Started

    Ready to integrate webhooks?

  • Sign up for Wsla - Get API access
  • Set up your server
  • Configure webhooks
  • Start receiving events!
  • Start Your Free Trial

    ---

    Related Articles:

    الأسئلة الشائعة

    How do webhooks work?

    Meta sends HTTP POST requests to your server when events occur.