code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

MessageBird WhatsApp API Integration with Next.js and Node.js (2025 Guide)

Complete tutorial: Integrate MessageBird WhatsApp Business API with Next.js 15 and Node.js. Learn to send messages, receive webhooks, handle templates, and build production-ready WhatsApp messaging apps.

Developer Guide: Integrating MessageBird WhatsApp with Next.js and Node.js

This comprehensive guide shows you how to integrate the MessageBird WhatsApp Business API into a Next.js application. You'll build a production-ready system that sends and receives WhatsApp messages, leveraging Next.js API routes, Node.js runtime, and MessageBird's WhatsApp Business platform.

This integration enables businesses to engage customers directly on WhatsApp for customer support, notifications, alerts, two-factor authentication, and automated messaging – all managed through a modern, scalable Next.js application. Whether you're building a customer service platform, notification system, or marketing automation tool, this guide provides the foundation for WhatsApp Business API integration.

Important: MessageBird rebranded to Bird in February 2024, though the API endpoints, SDK (messagebird npm package), and functionality remain unchanged. This guide uses the original MessageBird SDK and terminology interchangeably with Bird.

<!-- DEPTH: Lacks information about WhatsApp Business API pricing model, message costs, and rate limits (Priority: High) --> <!-- GAP: Missing discussion of WhatsApp 24-hour message window policy and conversation pricing (Type: Critical) --> <!-- GAP: No mention of WhatsApp Business verification requirements or Business Manager setup (Type: Critical) -->

Project Overview and Goals

What You'll Build:

A Next.js application with the following capabilities:

  1. API endpoint to send outbound WhatsApp messages (text and pre-approved templates) via MessageBird.
  2. API endpoint configured as a MessageBird webhook to receive inbound WhatsApp messages.
  3. Secure handling of API credentials and webhook secrets.
  4. Basic logging and error handling for message operations.
  5. (Optional) Basic database integration to log message history.
<!-- DEPTH: Missing concrete use case examples for different business scenarios (Priority: Medium) -->

Problem Solved:

This guide helps you programmatically interact with customers on WhatsApp using MessageBird, integrated within Next.js. It simplifies the process compared to managing the complexities of the direct WhatsApp Business API infrastructure.

<!-- GAP: Lacks comparison of MessageBird vs. direct WhatsApp Business API vs. competitors (Type: Substantive) -->

Technologies Used:

  • Next.js: A React framework for building full-stack web applications, providing server-side rendering, static site generation, and API routes for building RESTful endpoints.
  • Node.js: The JavaScript runtime environment used by Next.js for backend operations.
  • MessageBird: A communication platform as a service (CPaaS) providing APIs for various channels, including WhatsApp. You'll use their Node.js SDK.
  • dotenv: For managing environment variables securely.
  • (Optional) Prisma: A modern database toolkit for Node.js and TypeScript, used here for optional message logging.
  • (Optional) PostgreSQL/SQLite: Relational databases for storing message logs.

System Architecture:

text
+-----------------+      +---------------------+      +-----------------+      +----------+
| User/Client App |----->| Next.js Application |----->| MessageBird API |----->| WhatsApp |
| (Browser/API)   |      | (API Routes)        |<-----| (Webhooks)      |<-----|          |
+-----------------+      +---------------------+      +-----------------+      +----------+
                         |                     |
                         | (Optional)          |
                         | +-----------------+ |
                         | | Database        | |
                         | | (Prisma)        | |
                         | +-----------------+ |
                         +---------------------+
<!-- DEPTH: Architecture diagram lacks detail about security boundaries, data flow, and failure points (Priority: High) --> <!-- GAP: Missing discussion of scalability considerations and architectural trade-offs (Type: Substantive) -->

Prerequisites:

  • Node.js (v20 LTS or v22 LTS recommended for 2025; Next.js 15 requires minimum v18.18.0, but v18 was deprecated in Next.js 15.4) and npm/yarn installed.
  • A MessageBird account with access to the WhatsApp Business API.
  • A WhatsApp Business channel configured in your MessageBird dashboard (follow their onboarding quickstart).
  • A registered and approved WhatsApp number linked to your MessageBird channel (must be E.164 format: +[country code][number], up to 15 digits).
  • Basic understanding of Next.js, React, and API concepts.
  • (Optional) Docker and Docker Compose for containerized database setup.
  • (Optional) ngrok or alternatives like Pinggy, Cloudflare Tunnel, or Tunnelmole for testing webhooks locally. Free ngrok URLs are temporary and change each restart, requiring webhook URL updates in MessageBird; consider Pinggy (unlimited bandwidth, $3/month) or Cloudflare Tunnel (free, no bandwidth limits) for persistent URLs during development.
<!-- GAP: No step-by-step guidance on obtaining WhatsApp Business API access through MessageBird (Type: Critical) --> <!-- DEPTH: Prerequisites lack time estimates and difficulty levels (Priority: Medium) -->

Expected Outcome:

A functional Next.js application that sends text and template messages via MessageBird's WhatsApp API and receives incoming messages through a configured webhook. This guide provides a functional starting point. While it includes patterns for production (security checks, logging suggestions), the core code examples use basic constructs (like console.log) and include placeholders (// TODO:) requiring further implementation for a truly production-ready system.


1. Set Up Your Next.js Project for MessageBird WhatsApp Integration

Initialize your Next.js project and install the necessary dependencies for WhatsApp Business API integration.

1.1 Create Next.js App:

Open your terminal and run:

bash
npx create-next-app@latest messagebird-whatsapp-nextjs --typescript --eslint --tailwind --src-dir --app --import-alias "@/*"
cd messagebird-whatsapp-nextjs

(Adjust flags like --typescript, --tailwind based on your preferences. This guide assumes TypeScript and the src/ directory structure).

<!-- DEPTH: Missing explanation of why these specific flags are recommended (Priority: Low) -->

1.2 Install Dependencies:

Install the MessageBird Node.js SDK and dotenv for environment variables. If using Prisma, install it as well.

bash
npm install messagebird dotenv
# Optional: Install Prisma for database logging (Prisma 6.7.0+ compatible with Next.js 15.3.1+)
npm install prisma @prisma/client
# Optional: Initialize Prisma (choose your database provider)
npx prisma init --datasource-provider postgresql # or sqlite, mysql, etc.
<!-- GAP: No mention of messagebird SDK version compatibility or known issues (Type: Substantive) -->

1.3 Configure Environment Variables:

Create a file named .env.local in your project root. This file stores sensitive credentials and configuration. Never commit this file to version control.

dotenv
# .env.local

# MessageBird Credentials (Get from MessageBird Dashboard -> Developers -> API Access)
MESSAGEBIRD_API_KEY="YOUR_LIVE_OR_TEST_API_KEY"

# MessageBird WhatsApp Channel ID (Get from MessageBird Dashboard -> Channels -> WhatsApp -> Your Channel)
MESSAGEBIRD_CHANNEL_ID="YOUR_WHATSAPP_CHANNEL_ID"

# MessageBird Webhook Signing Secret (Generate/Get from Webhook settings in MessageBird Dashboard)
MESSAGEBIRD_WEBHOOK_SECRET="YOUR_WEBHOOK_SIGNING_SECRET"

# (Optional) Database URL for Prisma
# Example for PostgreSQL using Docker Compose (see section 6)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
# Example for SQLite
# DATABASE_URL="file:./dev.db"

# Base URL for your deployed application (needed for setting the webhook)
# For local development with ngrok, this might be like: https://xxxxx.ngrok-free.app
# For production, it will be your domain: https://yourapp.com
NEXT_PUBLIC_APP_URL="http://localhost:3000" # Default, update as needed
<!-- GAP: Missing guidance on managing secrets in production (AWS Secrets Manager, Vercel env vars, etc.) (Type: Critical) --> <!-- GAP: No discussion of Test API keys vs. Live API keys and when to use each (Type: Substantive) -->

Explanation:

  • MESSAGEBIRD_API_KEY: Authenticates your application with the MessageBird API. Use your Live key for production. Caution: Never commit your .env.local file containing live keys to version control. Use Test keys during development when possible.
  • MESSAGEBIRD_CHANNEL_ID: Identifies the specific WhatsApp channel you configured in MessageBird (a long alphanumeric string found in Dashboard > Channels > WhatsApp).
  • MESSAGEBIRD_WEBHOOK_SECRET: Verifies that incoming webhook requests genuinely originate from MessageBird. MessageBird signs requests using HMAC-SHA256 with this secret, sent via the MessageBird-Signature-JWT header.
  • DATABASE_URL: Connection string for Prisma if you're implementing database logging.
  • NEXT_PUBLIC_APP_URL: The publicly accessible base URL of your application, used when configuring the webhook URL in the MessageBird dashboard.
<!-- DEPTH: Lacks detailed security best practices for credential rotation (Priority: Medium) -->

1.4 Project Structure:

Your relevant project structure should look similar to this:

text
messagebird-whatsapp-nextjs/
├── src/
│   ├── app/
│   │   ├── api/                    # API Routes
│   │   │   ├── send-whatsapp/      # Endpoint for sending messages
│   │   │   │   └── route.ts
│   │   │   ├── messagebird-webhook/ # Endpoint for receiving messages
│   │   │   │   └── route.ts
│   │   │   └── health/             # Basic health check
│   │   │       └── route.ts
│   │   ├── page.tsx                # Example frontend page (optional)
│   │   └── layout.tsx
│   ├── lib/                        # Utility functions/SDK initialization
│   │   ├── messagebird.ts
│   │   └── prisma.ts              # (Optional) Prisma client instance
│   └── ... (other Next.js files)
├── prisma/                         # (Optional) Prisma schema and migrations
│   └── schema.prisma
├── .env.local                      # Environment variables (DO NOT COMMIT)
├── next.config.mjs
├── package.json
└── tsconfig.json

1.5 Initialize MessageBird SDK:

Create a utility file to initialize the MessageBird client.

typescript
// src/lib/messagebird.ts
import messagebird from 'messagebird';

// Ensure environment variable is loaded (though Next.js handles .env.local automatically)
if (!process.env.MESSAGEBIRD_API_KEY) {
  throw new Error("Missing MESSAGEBIRD_API_KEY environment variable");
}

const mbClient = messagebird(process.env.MESSAGEBIRD_API_KEY);

export default mbClient;

This initializes the SDK using the API key from your environment variables, making it ready to use in your API routes.

<!-- DEPTH: Missing discussion of SDK client lifecycle, connection pooling, or singleton patterns (Priority: Medium) --> <!-- GAP: No error handling for invalid API keys or network failures during initialization (Type: Substantive) -->

2. How to Send WhatsApp Messages Using MessageBird API

Create the Next.js API route responsible for sending outbound WhatsApp messages through MessageBird. This endpoint handles both text messages and pre-approved WhatsApp templates for business-initiated conversations.

<!-- GAP: Missing overview of WhatsApp message types and when to use each (Type: Substantive) --> <!-- GAP: No discussion of message templates approval process and timeline (Type: Critical) -->

2.1 Create the API Route:

Create the file src/app/api/send-whatsapp/route.ts.

typescript
// src/app/api/send-whatsapp/route.ts
import { NextRequest, NextResponse } from 'next/server';
import mbClient from '@/lib/messagebird'; // Import initialized client
import { MessageBird } from 'messagebird/types'; // Import types for better DX

// Define expected request body structure (using interface or Zod for validation)
interface SendMessageRequestBody {
  to: string; // Recipient phone number in E.164 format (e.g., +14155552671). Required: +[country code][number], up to 15 digits. Excludes short codes and numbers from Crimea, Cuba, Iran, North Korea, Syria.
  text?: string; // For simple text messages
  template?: { // For template messages
    name: string; // Approved template name
    language: string; // e.g., 'en_US'
    components?: MessageBird.MessageRequestTemplate['components']; // Parameters
  };
}

export async function POST(request: NextRequest) {
  console.log("Received request to /api/send-whatsapp"); // Note: Replace with structured logger in production

  let body: SendMessageRequestBody;
  try {
    body = await request.json();
    console.log("Request body:", body); // Note: Replace with structured logger in production

    // Basic validation (consider using Zod for robust validation)
    if (!body.to || (!body.text && !body.template)) {
      console.error("Validation failed: Missing 'to' or message content ('text'/'template')"); // Note: Replace with structured logger in production
      return NextResponse.json({ error: "Missing 'to' or message content ('text'/'template')" }, { status: 400 });
    }
    if (!process.env.MESSAGEBIRD_CHANNEL_ID) {
        console.error("Configuration error: Missing MESSAGEBIRD_CHANNEL_ID"); // Note: Replace with structured logger in production
        return NextResponse.json({ error: "Server configuration error: Channel ID missing" }, { status: 500 });
    }

  } catch (error) {
    console.error("Failed to parse request body:", error); // Note: Replace with structured logger in production
    return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
  }

  const params: MessageBird.MessageRequest = {
    to: body.to,
    from: process.env.MESSAGEBIRD_CHANNEL_ID, // Your MessageBird WhatsApp Channel ID
    type: 'text', // Default to text, override if template is present
    content: {},
  };

  // Construct message content based on request
  if (body.template) {
    params.type = 'hsm'; // HSM (Highly Structured Message) is used for templates
    params.content = {
      hsm: {
        // IMPORTANT: Replace with your WABA (WhatsApp Business Account) template namespace!
        // Find in MessageBird Dashboard > Templates > Template Details (e.g., 'cdb2df51_9816_c754_c5a4_64cdabdcad3e')
        // The namespace is a bundle of language packs for your business, required for template message routing.
        namespace: 'your_messagebird_template_namespace', // Get this from MessageBird template details
        templateName: body.template.name,
        language: {
          policy: 'deterministic',
          code: body.template.language,
        },
        components: body.template.components || [], // Pass parameters if provided
      },
    };
    console.log("Sending template message:", params); // Note: Replace with structured logger in production
  } else if (body.text) {
    params.type = 'text';
    params.content = { text: body.text };
    console.log("Sending text message:", params); // Note: Replace with structured logger in production
  } else {
      // This case should be caught by initial validation, but good to have defensive coding
      console.error("No message content provided."); // Note: Replace with structured logger in production
      return NextResponse.json({ error: "No message content provided" }, { status: 400 });
  }


  try {
    // Wrap the callback-based SDK method in a Promise
    const result = await new Promise<MessageBird.Message>((resolve, reject) => {
      mbClient.messages.create(params, (err, response) => {
        if (err) {
          console.error("MessageBird API Error:", err); // Note: Replace with structured logger in production
          // Extract more specific error info if available
          const errorDetails = err.errors ? JSON.stringify(err.errors) : err.message;
          reject(new Error(`MessageBird API Error: ${errorDetails}`));
        } else if (response) {
          console.log("MessageBird API Success:", response); // Note: Replace with structured logger in production
          resolve(response);
        } else {
          reject(new Error("MessageBird API returned undefined response"));
        }
      });
    });

    return NextResponse.json({ success: true, messageId: result.id, status: result.status }, { status: 201 }); // 201 Created is suitable

  } catch (error: any) {
    console.error("Error sending message:", error); // Note: Replace with structured logger in production
    // Return a generic error to the client, log the specific error server-side
    return NextResponse.json({ success: false, error: 'Failed to send message', details: error.message }, { status: 500 });
  }
}

// Optional: Add a GET handler for basic testing or documentation
export async function GET() {
  return NextResponse.json({ message: "POST to this endpoint to send a WhatsApp message." });
}
<!-- GAP: Missing rate limiting implementation to prevent API abuse (Type: Critical) --> <!-- GAP: No authentication/authorization check for the API endpoint (Type: Critical) --> <!-- DEPTH: Lacks detailed explanation of MessageBird API error codes and how to handle each (Priority: High) --> <!-- GAP: Missing retry logic for transient failures (Type: Substantive) -->

Explanation:

  1. Import Dependencies: Import NextRequest, NextResponse, the initialized mbClient, and MessageBird types.
  2. Define Request Body: An interface SendMessageRequestBody defines the expected structure of incoming POST requests. Use a validation library like Zod for production.
  3. POST Handler: This asynchronous function handles POST requests.
  4. Parse & Validate: Parse the JSON body and perform basic validation to ensure the recipient (to) and message content (text or template) are present. Check for the MESSAGEBIRD_CHANNEL_ID.
  5. Construct Parameters: Build the params object required by the messagebird.messages.create method.
    • to: Recipient number.
    • from: Your MessageBird WhatsApp Channel ID (from .env.local).
    • type: Set to 'text' or 'hsm' (for templates).
    • content: An object containing either { text: "…" } or { hsm: { … } }.
  6. Template Handling: If body.template exists, set type to hsm and construct the hsm object.
    • namespace: Crucial: Find your template namespace in the MessageBird dashboard (usually associated with your Facebook Business Manager or template details). Replace 'your_messagebird_template_namespace' with this value.
    • templateName: The name of your pre-approved template.
    • language: The language code (e.g., en_US).
    • components: An array to pass parameters (variables) for your template placeholders. The structure depends on your template (header, body, buttons). Refer to MessageBird's documentation for component structure.
  7. Text Handling: If body.text exists, set type to text and content accordingly.
  8. API Call: Use mbClient.messages.create to send the message. (Note: This example wraps the callback-based SDK method in a Promise for use with async/await. Check the documentation for the specific version of the messagebird SDK you're using; newer versions might offer direct Promise support.)
  9. Error Handling: A try…catch block handles potential errors during parsing, validation, or the API call itself. Specific MessageBird API errors are logged server-side.
  10. Response: Return a JSON response indicating success (with message ID) or failure.
  11. Logging: Uses console.log/console.error for simplicity in this example. For production, replace with a structured logger (see Section 5.2).
<!-- DEPTH: Code walkthrough could benefit from visual flow diagram showing request/response flow (Priority: Medium) --> <!-- GAP: Missing example request/response payloads for both text and template messages (Type: Substantive) -->

3. How to Receive WhatsApp Messages Using MessageBird Webhooks

Create the webhook endpoint that MessageBird calls whenever your WhatsApp Business number receives an inbound message or delivery status update. This enables real-time two-way messaging capabilities in your Next.js application.

<!-- GAP: Missing overview of webhook event types and their meanings (Type: Substantive) --> <!-- DEPTH: Lacks discussion of webhook reliability, retry mechanisms, and idempotency (Priority: High) -->

3.1 Create the Webhook API Route:

Create the file src/app/api/messagebird-webhook/route.ts.

typescript
// src/app/api/messagebird-webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
import { MessageBird } from 'messagebird/types'; // Webhook payload types
// Optional: Import Prisma client if logging messages
// import prisma from '@/lib/prisma';

// --- Webhook Signature Verification ---
async function verifySignature(request: NextRequest): Promise<boolean> {
  // MessageBird signs webhooks using HMAC-SHA256 with MessageBird-Signature-JWT header (updated 2024)
  const signature = request.headers.get('messagebird-signature');
  const timestamp = request.headers.get('messagebird-request-timestamp');
  const webhookSecret = process.env.MESSAGEBIRD_WEBHOOK_SECRET;

  if (!signature || !timestamp || !webhookSecret) {
    console.warn('Webhook verification failed: Missing signature, timestamp, or secret'); // Note: Replace with structured logger in production
    return false;
  }

  // Clone the request to read the body safely
  const requestClone = request.clone();
  const bodyText = await requestClone.text(); // Read body as raw text

  // Construct the signed payload string (MessageBird format: timestamp.secret.body)
  const signedPayload = `${timestamp}.${webhookSecret}.${bodyText}`;

  // Calculate the expected signature using HMAC-SHA256
  const expectedSignature = crypto
    .createHmac('sha256', webhookSecret)
    .update(signedPayload)
    .digest('hex');

  // Compare signatures securely using timing-safe comparison to prevent timing attacks
  const isValid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));

  if (!isValid) {
      console.warn('Webhook verification failed: Invalid signature'); // Note: Replace with structured logger in production
      console.log(`Received Signature: ${signature}`); // Note: Replace with structured logger in production
      console.log(`Expected Signature: ${expectedSignature}`); // Note: Replace with structured logger in production
      // console.log(`Timestamp: ${timestamp}`); // Be cautious logging timestamps if needed for debugging
      // console.log(`Body: ${bodyText}`); // Be very cautious logging raw body content
  }


  return isValid;
}

// --- Main Webhook Handler ---
export async function POST(request: NextRequest) {
  console.log("Received POST request to /api/messagebird-webhook"); // Note: Replace with structured logger in production

  // 1. Verify the signature FIRST
  const isVerified = await verifySignature(request);
  if (!isVerified) {
    console.error("Webhook signature verification failed."); // Note: Replace with structured logger in production
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  console.log("Webhook signature verified successfully."); // Note: Replace with structured logger in production

  // 2. Parse the JSON body (only after verification)
  let payload: MessageBird.WebhookPayload;
  try {
    payload = await request.json();
    console.log('Received webhook payload:', JSON.stringify(payload, null, 2)); // Log the full payload for debugging - Replace with structured logger in production
  } catch (error) {
    console.error('Failed to parse webhook payload:', error); // Note: Replace with structured logger in production
    return NextResponse.json({ error: 'Invalid payload' }, { status: 400 });
  }

  // 3. Process the event based on type
  try {
    switch (payload.type) {
      case 'message.created':
      case 'message.updated': // Handle status updates (sent, delivered, read)
        const messageEvent = payload as MessageBird.MessageEvent; // Type assertion
        console.log(`Processing ${payload.type} event for message ID: ${messageEvent.message.id}`); // Note: Replace with structured logger in production
        console.log(`Direction: ${messageEvent.message.direction}, Status: ${messageEvent.message.status}`); // Note: Replace with structured logger in production

        // Only process incoming messages ('mo' - mobile originated)
        if (messageEvent.message.direction === 'mo') {
          console.log(`Received message from ${messageEvent.message.originator}:`); // Note: Replace with structured logger in production
          console.log(`Type: ${messageEvent.message.content.type}, Content:`, messageEvent.message.content); // Note: Replace with structured logger in production

          // --- Optional: Log incoming message to Database ---
          /*
          if (prisma) {
              try {
                  await prisma.messageLog.create({
                      data: {
                          messageId: messageEvent.message.id,
                          channelId: messageEvent.message.channelId,
                          conversationId: messageEvent.message.conversationId,
                          direction: 'incoming',
                          status: messageEvent.message.status ?? 'received',
                          sender: messageEvent.message.originator ?? 'unknown',
                          recipient: messageEvent.message.recipient, // Your number/channel
                          content: JSON.stringify(messageEvent.message.content), // Store full content as JSON string
                          receivedAt: new Date(messageEvent.message.createdDatetime ?? Date.now()),
                          platform: 'whatsapp'
                      }
                  });
                  console.log(`Logged incoming message ${messageEvent.message.id} to DB`); // Note: Replace with structured logger in production
              } catch (dbError) {
                  console.error("Failed to log incoming message to DB:", dbError); // Note: Replace with structured logger in production
                  // Decide if you should still return 200 OK to MessageBird
              }
          }
          */
          // --- End Optional DB Log ---

          // TODO: Implement your business logic here based on the incoming message
          // Example: Route to support, trigger automated reply, etc.

        } else if (messageEvent.message.direction === 'mt') { // Mobile Terminated (outgoing) status updates
            console.log(`Status update for outgoing message ${messageEvent.message.id}: ${messageEvent.message.status}`); // Note: Replace with structured logger in production
            // --- Optional: Update message status in Database ---
            /*
            if (prisma) {
                 try {
                    await prisma.messageLog.updateMany({ // Use updateMany in case the initial record wasn't created yet
                        where: { messageId: messageEvent.message.id },
                        data: {
                            status: messageEvent.message.status ?? 'unknown',
                            // Optionally update other fields like deliveredAt, readAt based on status
                        }
                    });
                    console.log(`Updated status for message ${messageEvent.message.id} to ${messageEvent.message.status} in DB`); // Note: Replace with structured logger in production
                } catch (dbError) {
                    console.error("Failed to update message status in DB:", dbError); // Note: Replace with structured logger in production
                }
            }
            */
           // --- End Optional DB Log ---
        }
        break;

      // Handle other event types if needed (e.g., conversation events)
      // case 'conversation.created':
      //   // Handle conversation events
      //   break;

      default:
        console.log(`Received unhandled event type: ${payload.type}`); // Note: Replace with structured logger in production
    }

    // 4. Acknowledge receipt to MessageBird
    // Respond quickly with a 200 OK, even if background processing continues.
    return NextResponse.json({ success: true }, { status: 200 });

  } catch (processingError) {
    console.error('Error processing webhook payload:', processingError); // Note: Replace with structured logger in production
    // Still return 200 OK if possible to prevent MessageBird retries,
    // but log the error for investigation. Use a 500 for critical failures.
    // Depending on the error, you might want to respond differently.
    return NextResponse.json({ success: false, error: 'Failed to process webhook' }, { status: 500 });
  }
}

// Optional: Add GET handler for basic testing/verification during webhook setup
export async function GET() {
  console.log("Received GET request to /api/messagebird-webhook"); // Note: Replace with structured logger in production
  // MessageBird doesn't typically use GET for verification, but it can be useful for simple reachability tests.
  return NextResponse.json({ message: "MessageBird Webhook endpoint is active. Use POST for events." });
}
<!-- GAP: Missing implementation of idempotency keys to handle duplicate webhook deliveries (Type: Critical) --> <!-- DEPTH: Signature verification explanation lacks detail on replay attack prevention (Priority: High) --> <!-- GAP: No discussion of webhook timeout handling and async processing patterns (Type: Substantive) -->

Explanation:

  1. Import Dependencies: Includes crypto for signature verification and optional prisma.
  2. verifySignature Function:
    • Retrieves the messagebird-signature, messagebird-request-timestamp headers, and your MESSAGEBIRD_WEBHOOK_SECRET.
    • Reads the raw request body without parsing it as JSON first. Cloning the request is essential as the body stream can only be read once.
    • Constructs the signedPayload string exactly as MessageBird requires: timestamp.webhookSecret.rawBody.
    • Calculates the expected HMAC SHA256 hash using your secret.
    • Uses crypto.timingSafeEqual for secure comparison to prevent timing attacks.
    • Returns true if signatures match, false otherwise. Logs warnings on failure.
  3. POST Handler:
    • Verify Signature: Call verifySignature immediately. If verification fails, return a 401 Unauthorized response and stop processing. This is critical for security.
    • Parse Payload: Only after verification succeeds, parse the JSON payload.
    • Process Event: Use a switch statement on payload.type to handle different events.
      • message.created / message.updated: Log details about the message, including direction (mo for incoming, mt for outgoing) and status.
      • Incoming Message Logic (mo): Extract sender (originator), content type, and content. Includes commented-out Prisma code for logging the message to a database. The // TODO: block is where you implement your specific business logic for responding to or acting upon received messages.
      • Outgoing Status Update Logic (mt): Log status updates for messages you sent. Includes commented-out Prisma code for updating the status in the database.
    • Acknowledge Receipt: Return a 200 OK response to MessageBird promptly. This signals that you've received the event. Delaying this response can cause MessageBird to retry sending the webhook.
    • Error Handling: Catch errors during payload processing and log them. Return 200 OK for non-critical processing errors to avoid webhook retries, but return 500 if the error prevents meaningful processing.
    • Logging: Uses console.log/console.error/console.warn for simplicity. For production, replace with a structured logger (see Section 5.2).
<!-- GAP: Missing concrete examples of business logic implementation for common use cases (Type: Substantive) --> <!-- DEPTH: TODO comment is not actionable without more guidance (Priority: High) -->

4. Configure MessageBird Dashboard for WhatsApp API Access

Connect your Next.js application to your MessageBird account by obtaining API credentials, configuring the WhatsApp channel, and setting up webhooks for message delivery.

<!-- DEPTH: Section lacks screenshots or visual guides showing dashboard navigation (Priority: High) -->

4.1 Obtain API Key:

  • Navigate to your MessageBird Dashboard.
  • Go to Developers > API access.
  • Copy your Live API key (or Test API key for development).
  • Paste this value into your .env.local file for MESSAGEBIRD_API_KEY.
<!-- GAP: No guidance on API key permissions and scopes (Type: Substantive) --> <!-- GAP: Missing information about API key regeneration and rotation procedures (Type: Substantive) -->

4.2 Obtain WhatsApp Channel ID:

  • In the MessageBird Dashboard, go to Channels.
  • Select your configured WhatsApp channel.
  • Find the Channel ID (usually a long alphanumeric string).
  • Paste this value into MESSAGEBIRD_CHANNEL_ID in your .env.local.
<!-- DEPTH: Lacks troubleshooting steps if channel ID is not visible or channel is not properly configured (Priority: Medium) -->

4.3 Configure Webhook:

  • In your WhatsApp Channel settings in the MessageBird Dashboard, find the Webhook or Events section.
  • You need to provide a publicly accessible URL for your webhook handler.
    • Production: Use your deployed application's URL: https://your-app-domain.com/api/messagebird-webhook
    • Local Development:
      1. Start your Next.js app: npm run dev
      2. Expose local server using a tunneling service:
        • ngrok (free, temporary URLs): ngrok http 3000 → Use the https URL (e.g., https://a1b2-c3d4.ngrok-free.app/api/messagebird-webhook). URLs change on restart.
        • Pinggy (recommended for development): ssh -p 443 -R0:localhost:3000 a.pinggy.io → Provides persistent URLs, unlimited bandwidth. Free tier available, paid plans start at $3/month.
        • Cloudflare Tunnel (free, persistent): Install cloudflared, run cloudflared tunnel --url http://localhost:3000 → No bandwidth limits, stable URLs.
        • Tunnelmole (open-source): tmole 3000 → Simple, fast, free for normal use.
      3. Use the tunnel URL in MessageBird: https://your-tunnel-url/api/messagebird-webhook
  • Paste the appropriate URL into the Webhook URL field in MessageBird.
  • Webhook Signing Secret:
    • MessageBird allows you to view or generate a Signing Key (or Secret) for your webhook.
    • Copy this secret value.
    • Paste it into MESSAGEBIRD_WEBHOOK_SECRET in your .env.local.
  • Select Events: Ensure you subscribe to relevant events, at least message.created and message.updated.
  • Save the webhook configuration in MessageBird.
<!-- GAP: Missing webhook testing procedures and verification steps (Type: Substantive) -->

4.4 Update NEXT_PUBLIC_APP_URL:

Ensure the NEXT_PUBLIC_APP_URL in your .env.local matches the base URL you used for the webhook (especially important if using ngrok or deploying).


5. Implement Error Handling and Logging for Production

Robust error handling and logging are essential for production WhatsApp Business API integrations. Proper logging helps you troubleshoot issues, track message delivery, and monitor system performance.

<!-- DEPTH: Section focuses only on logging setup, missing comprehensive error handling patterns (Priority: High) --> <!-- GAP: No discussion of monitoring, alerting, and observability tools (Type: Substantive) -->

5.1 Error Handling Strategy:

  • API Routes: Use try…catch blocks extensively in both send-whatsapp and messagebird-webhook routes.
  • Validation: Implement input validation (e.g., using Zod) early in the request lifecycle to catch malformed requests.
  • Specific Errors: Catch specific errors from the MessageBird SDK and database operations. Log detailed error information server-side (stack trace, request context).
  • Client Responses: Return clear, concise error messages to the client for API calls (/api/send-whatsapp), but avoid exposing sensitive internal details. For webhooks (/api/messagebird-webhook), prioritize returning a 200 OK quickly unless there's a critical failure preventing processing.
  • Environment Checks: Check for the existence of necessary environment variables on startup or early in request handling.
<!-- GAP: Missing concrete code examples showing proper error handling patterns (Type: Substantive) --> <!-- GAP: No discussion of error categorization and different handling strategies per error type (Type: Substantive) -->

5.2 Logging:

While console.log/console.error is used in this guide's examples for simplicity, replace it with a structured logging library for production (e.g., pino, winston).

  • Install Pino: npm install pino pino-pretty (pino-pretty for development).
  • Configure Logger: Create a logger instance (e.g., src/lib/logger.ts) and use it throughout your API routes.
typescript
// Example: src/lib/logger.ts (Basic Pino Setup)
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV !== 'production'
    ? { target: 'pino-pretty' } // Pretty print in development
    : undefined, // Default JSON output in production
});

export default logger;

// Usage in API routes:
// import logger from '@/lib/logger';
// logger.info('Processing request...');
// logger.error({ err: error }, 'Failed to send message');
  • Log Levels: Use appropriate levels (info, warn, error, debug).
  • Context: Include relevant context in logs (request ID, user ID if applicable, message ID).
  • Note: The code examples in Sections 2.1 and 3.1 use console.log for simplicity. For production applications, replace these with structured logging using a library like Pino, as demonstrated here.
<!-- GAP: Missing guidance on log aggregation, retention policies, and PII handling (Type: Critical) -->

Frequently Asked Questions (FAQ)

What Node.js version do I need for MessageBird WhatsApp integration with Next.js 15?

Use Node.js v20 LTS or v22 LTS for Next.js 15 in 2025. Next.js 15 requires minimum v18.18.0, but v18 was deprecated in Next.js 15.4. v20 and v22 provide long-term support and compatibility.

How do I format phone numbers for WhatsApp Business API?

WhatsApp requires E.164 format: +[country code][number], up to 15 digits total (e.g., +14155552671). Include the country code with no spaces or special characters. Landlines and mobile numbers work, but short codes and numbers from sanctioned countries (Crimea, Cuba, Iran, North Korea, Syria) are excluded.

What is the WABA template namespace in MessageBird?

The WABA (WhatsApp Business Account) template namespace is a unique identifier bundle for your business's message templates. Find it in MessageBird Dashboard > Templates > Template Details (format: cdb2df51_9816_c754_c5a4_64cdabdcad3e). This namespace is required for sending HSM (Highly Structured Message) template messages.

<!-- DEPTH: FAQ answers are surface-level, could provide more depth and examples (Priority: Medium) -->

How does MessageBird webhook signature verification work?

MessageBird signs webhooks using HMAC-SHA256 with the MessageBird-Signature-JWT header. Your application must construct the signed payload as timestamp.secret.body, compute the HMAC-SHA256 hash using your webhook secret, and compare it with the received signature using crypto.timingSafeEqual() to prevent timing attacks.

What's the difference between MessageBird and Bird?

MessageBird rebranded to Bird in February 2024 with 90% price cuts on SMS. However, the API endpoints, Node.js SDK (messagebird npm package), and WhatsApp functionality remain unchanged. Existing integrations continue working without modification.

Which webhook tunneling tool is best for local WhatsApp development?

For persistent URLs during development, use Pinggy ($3/month, unlimited bandwidth) or Cloudflare Tunnel (free, no bandwidth limits). While ngrok works, free URLs are temporary and change on restart, requiring frequent webhook URL updates in MessageBird. Tunnelmole offers an open-source alternative.

Can I use Prisma with Next.js 15 for logging WhatsApp messages?

Yes, Prisma 6.7.0+ is fully compatible with Next.js 15.3.1+. Use Prisma to log incoming and outgoing WhatsApp messages, track delivery status, and maintain conversation history. The guide includes commented code examples for database integration.

<!-- GAP: Missing Prisma schema example and migration instructions (Type: Substantive) -->

How do I send WhatsApp template messages vs. regular text messages?

Regular text messages set type: 'text' with content: { text: "…" }. Template messages set type: 'hsm' with your approved template name, WABA namespace, language code, and component parameters. Templates are required for business-initiated conversations or re-engagement after 24 hours of inactivity.

What are the benefits of using MessageBird WhatsApp API with Next.js?

MessageBird WhatsApp API with Next.js provides serverless deployment capabilities, easy API route management, built-in TypeScript support, and seamless integration with modern React frameworks. You get automatic scaling, edge network distribution, and simplified webhook handling through Next.js API routes.

How do I troubleshoot WhatsApp message delivery failures?

Check MessageBird API error codes in your logs, verify E.164 phone number formatting, ensure your WhatsApp channel is active, confirm template approval status, and validate webhook signature verification. For SMS-related troubleshooting, similar principles apply to phone number formatting and API authentication.

<!-- GAP: Missing FAQ about common errors and troubleshooting (Type: Substantive) --> <!-- GAP: No FAQ about deployment best practices and platform-specific considerations (Type: Substantive) --> <!-- GAP: Missing FAQ about testing strategies and tools (Type: Substantive) -->

Frequently Asked Questions

how to send whatsapp messages with next.js

You can send WhatsApp messages within a Next.js app using the MessageBird API and their Node.js SDK. Create an API route in your Next.js application that handles POST requests, constructs the message payload (text or template), and uses the MessageBird SDK to send the message via your WhatsApp Business Channel ID.

what is messagebird whatsapp business api

The MessageBird WhatsApp Business API is a service that lets developers programmatically send and receive WhatsApp messages. This guide integrates it with Next.js and Node.js to manage customer interactions, notifications, alerts, and more, all within a web application.

why use messagebird with next.js for whatsapp

MessageBird simplifies WhatsApp integration by handling the complexities of the direct WhatsApp Business API. Combined with Next.js's full-stack capabilities, developers can build robust WhatsApp communication features within a modern web framework, improving development efficiency.

when should I use a whatsapp template message

Use WhatsApp template messages for pre-approved, structured communications like order confirmations or appointment reminders. For general chat or support, send standard text messages. Template messages require a namespace and parameters defined in your MessageBird account.

can i receive whatsapp messages in my next.js app

Yes, by setting up a webhook. Configure a dedicated API route in your Next.js app to handle incoming messages. Then, in your MessageBird dashboard, configure your WhatsApp channel's webhook to point to this route, ensuring it's publicly accessible (e.g., via ngrok for development or your deployment domain for production).

how to set up messagebird webhook with next.js

Create an API route in Next.js to act as your webhook endpoint. In your MessageBird dashboard, configure your WhatsApp Channel to send events to this URL. Make sure to use a publicly accessible URL, such as one provided by ngrok during development. Implement signature verification to ensure security.

what is messagebird channel id and where to find it

The MessageBird Channel ID is a unique identifier for your WhatsApp Business Channel. You can find it in your MessageBird dashboard under Channels -> WhatsApp -> Your Channel. This ID is essential for sending and receiving messages.

how to verify messagebird webhook signature in next js

Webhook signature verification is crucial for security. Retrieve the 'messagebird-signature' and 'messagebird-request-timestamp' headers from the incoming request, along with your Webhook Signing Secret from your MessageBird dashboard. Construct the signed payload string using these and calculate the HMAC SHA256 hash. Compare the calculated hash with the 'messagebird-signature' using a constant-time comparison method like `crypto.timingSafeEqual`.

what is the purpose of .env.local file

The `.env.local` file stores sensitive information like your MessageBird API Key, Channel ID, Webhook Secret, and database URL. This file should never be committed to version control. Next.js automatically loads environment variables from this file during development.

how to integrate messagebird with next.js api routes

First, install the 'messagebird' npm package. Then, initialize the MessageBird client in a utility file (`lib/messagebird.ts`) using your API Key from `.env.local`. Import and use this initialized client in your API routes to send messages and interact with the MessageBird API.

what are the prerequisites for messagebird whatsapp integration

You need a Node.js environment, a MessageBird account, a configured WhatsApp Business channel within MessageBird, a registered and approved WhatsApp number, a basic understanding of Next.js and APIs, and optionally Docker, a database, and ngrok for local testing.

how to handle incoming whatsapp messages in next.js

Your webhook route should process 'message.created' events. Extract the message content, sender, and other relevant details from the payload. Implement your business logic based on these details: route to support, trigger an automated reply, store message data, etc. Remember to always respond to the webhook with a 200 OK status, even if your processing continues asynchronously.

what is ngrok and why is it needed

ngrok creates temporary public URLs for your locally running services. It is useful for testing webhooks during development, allowing MessageBird to deliver events to your local machine. Note that free ngrok URLs change each time you restart ngrok, so they are not suitable for permanent webhook configurations.

how to use prisma for message logging with messagebird

Install Prisma and the necessary database connectors. Define your data models in `prisma/schema.prisma`. Use the Prisma Client in your webhook handler to create database records for incoming messages, storing details like sender, content, and timestamp.

what is the system architecture for integrating messagebird and next.js

The user/client application interacts with the Next.js frontend and API routes. These routes communicate with the MessageBird API to send and receive WhatsApp messages. Optionally, a database (using Prisma) can log message history.