WhatsApp Webhooks Integration Guide for Developers

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:

1. User sends message to your WhatsApp
2. WhatsApp sends HTTP POST to your webhook
3. Your server receives the payload
4. Your server processes and responds
5. 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?

  1. Sign up for WSLA - Get API access
  2. Set up your server
  3. Configure webhooks
  4. Start receiving events!

Start Your Free Trial

---

Related Articles:

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

How do webhooks work?

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