DEV Community

L.
L.

Posted on

🟒 Build a WhatsApp Webhook Server Using the Official Facebook Business SDK in TypeScript πŸš€

We'll build a webhook server that:

  • Receives incoming messages and status updates
  • Verifies signatures securely
  • Uses the official SDK for parsing and handling events
  • Is ready to be extended into a chatbot or customer support tool

TL;DR: Learn how to create a secure, production-ready webhook server in Node.js + TypeScript that receives messages from WhatsApp using the official Facebook Node.js Business SDK. This setup works directly with WhatsApp Business API (via Meta/Facebook), so it’s perfect for chatbots, automation, and customer messaging integrations.


πŸ’¬ Why Use the Facebook Business SDK?

Instead of manually parsing WhatsApp webhook payloads, you can use the officially supported SDK:

npm install --save facebook-nodejs-business-sdk
Enter fullscreen mode Exit fullscreen mode

This gives you:

  • Verified request signature validation πŸ”
  • Built-in message/status parsing βœ…
  • Consistent event types 🧩
  • Better error handling πŸ›‘οΈ

Perfect for building scalable WhatsApp integrations.


🧰 Tech Stack

We'll use:

  • Node.js + Express.js
  • TypeScript
  • Express middleware
  • facebook-nodejs-business-sdk
  • Optional: Zod for schema validation
  • Optional: dotenv for environment variables

πŸ“¦ Step 1: Setup Your Project

mkdir whatsapp-webhook-server
cd whatsapp-webhook-server
npm init -y
npm install express body-parser dotenv cors helmet morgan
npm install --save facebook-nodejs-business-sdk
npm install --save-dev typescript ts-node @types/express zod
Enter fullscreen mode Exit fullscreen mode

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}
Enter fullscreen mode Exit fullscreen mode

πŸ—οΈ Step 2: Basic Server Setup

Create src/index.ts:

import express from 'express';
import bodyParser from 'body-parser';
import dotenv from 'dotenv';

dotenv.config();

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(bodyParser.json());
app.use(express.json());

// Health check route
app.get('/health', (req, res) => {
  res.status(200).send('βœ… WhatsApp webhook server is running!');
});

app.listen(PORT, () => {
  console.log(`πŸš€ WhatsApp webhook server is listening on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Run it with:

npx ts-node src/index.ts
Enter fullscreen mode Exit fullscreen mode

Test:

curl http://localhost:3000/health
Enter fullscreen mode Exit fullscreen mode

Output: βœ… WhatsApp webhook server is running!


πŸͺ Step 3: Create WhatsApp Webhook Endpoint

WhatsApp sends JSON payloads to your webhook URL when users send messages or message statuses change.

Let’s create /whatsapp endpoint using the SDK.

Create src/webhookHandler.ts:

import { Request, Response } from 'express';
import { Webhook, Message } from 'facebook-nodejs-business-sdk';

const APP_SECRET = process.env.WHATSAPP_APP_SECRET as string;

export const handleWhatsAppWebhook = (req: Request, res: Response) => {
  try {
    // Initialize Webhook class with app secret
    const webhook = new Webhook(APP_SECRET);

    // Validate and parse the request
    const isValid = webhook.validateRequest(req);
    if (!isValid) {
      return res.status(400).send('Invalid signature');
    }

    const payload = webhook.getPayload();

    // Handle challenge for initial setup
    if (payload.object === 'whatsapp_business_account') {
      payload.entry.forEach((entry: any) => {
        entry.changes.forEach((change: any) => {
          const value = change.value;
          const message = value.messages?.[0];

          if (message) {
            console.log("πŸ’¬ Incoming message:", message);
            // You can now reply using WhatsApp Cloud API
          }

          const status = value.statuses?.[0];
          if (status) {
            console.log("πŸ”„ Message status updated:", status);
          }
        });
      });

      // Confirm receipt
      return res.status(200).send('EVENT_RECEIVED');
    }

    res.status(400).send('Unknown object type');
  } catch (err) {
    console.error("⚠️ Error handling webhook:", err);
    res.status(500).send('Internal Server Error');
  }
};
Enter fullscreen mode Exit fullscreen mode

Add route in index.ts:

import { handleWhatsAppWebhook } from './webhookHandler';

app.post('/whatsapp', handleWhatsAppWebhook);
Enter fullscreen mode Exit fullscreen mode

🌍 Step 4: Expose Locally for Testing

Use ngrok to get a public URL:

npx ngrok http 3000
Enter fullscreen mode Exit fullscreen mode

You’ll get something like https://abc123.ngrok.io.

Use this URL when setting up your webhook in the Meta Developer Portal under your WhatsApp Business App settings.

Set the callback URL to:

https://abc123.ngrok.io/whatsapp
Enter fullscreen mode Exit fullscreen mode

And verify token to match your app logic.


πŸ§ͺ Step 5: Add Schema Validation (Optional)

Validate the shape of incoming WhatsApp events before processing.

Example schema for message object:

// src/schemas/whatsappSchema.ts
import { z } from 'zod';

export const WhatsAppMessageSchema = z.object({
  from: z.string(),
  id: z.string(),
  timestamp: z.string(),
  text: z.object({ body: z.string() }).optional(),
  type: z.enum(['text', 'image', 'video', 'document', 'audio']),
});
Enter fullscreen mode Exit fullscreen mode

In handler:

import { WhatsAppMessageSchema } from '../schemas/whatsappSchema';

if (message) {
  const parsed = WhatsAppMessageSchema.safeParse(message);
  if (parsed.success) {
    console.log("βœ… Valid message received:", parsed.data);
  } else {
    console.warn("❌ Invalid message structure");
  }
}
Enter fullscreen mode Exit fullscreen mode

Now malformed messages are caught early ⚠️.


πŸ”„ Step 6: Acknowledge Fast, Process Later

Always respond quickly to avoid retries.

Refactor to process asynchronously:

setTimeout(async () => {
  await processIncomingMessage(parsed.data);
}, 0);

res.status(200).send("EVENT_RECEIVED");
Enter fullscreen mode Exit fullscreen mode

Or better yet, push to a queue system like BullMQ or Redis.


🧼 Step 7: Prepare for Production

  • Store logs securely (use Winston/Pino)
  • Set up rate limiting (express-rate-limit)
  • Rotate secrets regularly
  • Deploy to Vercel, Render, AWS, or Heroku πŸš€
  • Use HTTPS (required by WhatsApp)

βœ… Summary: What You’ve Built

Feature Description
βœ”οΈ Webhook Receiver /whatsapp endpoint accepting POSTs
βœ”οΈ Signature Verification Uses Facebook SDK for HMAC validation
βœ”οΈ Event Parsing Parses messages/statuses out-of-the-box
βœ”οΈ Async Processing Acknowledge fast, process later
βœ”οΈ External Integration Works with WhatsApp Business API
βœ”οΈ Secure by Default Uses SDK, logging, secret checks

🚨 Final Tips

  • Always respond within seconds ⚑
  • Never throw unhandled errors ⚠️
  • Log everything during dev πŸ“œ
  • Store events for audit/retry (optional) πŸ—‚οΈ
  • Rotate secrets regularly πŸ”

πŸ“’ Share This Post

If you found this useful, share it with your fellow devs and help others build secure, robust WhatsApp webhook servers too!

Top comments (0)