Developer Guide: Integrating MessageBird WhatsApp with Next.js and Node.js
This guide provides a comprehensive walkthrough for integrating the MessageBird WhatsApp Business API into a Next.js application. We'll build a system capable of sending and receiving WhatsApp messages, leveraging the power of Next.js for the frontend and API routes, Node.js for the backend logic, and the robust MessageBird platform for WhatsApp communication.
This integration enables businesses to engage customers directly on WhatsApp for support, notifications, alerts, and more, all managed through a modern web application framework.
Project Overview and Goals
What We'll Build:
We will create a Next.js application with the following capabilities:
- An API endpoint to send outbound WhatsApp messages (text and pre-approved templates) via MessageBird.
- An API endpoint configured as a MessageBird webhook to receive inbound WhatsApp messages.
- Secure handling of API credentials and webhook secrets.
- Basic logging and error handling for message operations.
- (Optional) Basic database integration to log message history.
Problem Solved:
This guide addresses the need for developers to programmatically interact with customers on WhatsApp using a reliable third-party provider like MessageBird, integrated within a popular full-stack framework like Next.js. It simplifies the process compared to managing the complexities of the direct WhatsApp Business API infrastructure.
Technologies Used:
- Next.js: A React framework for building full-stack web applications, providing server-side rendering, static site generation, and API routes.
- 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. We'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:
+-----------------+ +---------------------+ +-----------------+ +----------+
| User/Client App |----->| Next.js Application |----->| MessageBird API |----->| WhatsApp |
| (Browser/API) | | (API Routes) |<-----| (Webhooks) |<-----| |
+-----------------+ +---------------------+ +-----------------+ +----------+
| |
| (Optional) |
| +-----------------+ |
| | Database | |
| | (Prisma) | |
| +-----------------+ |
+---------------------+
Prerequisites:
- Node.js (v18 or later recommended) 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.
- Basic understanding of Next.js_ React_ and API concepts.
- (Optional) Docker and Docker Compose for containerized database setup.
- (Optional)
ngrok
or a similar tool for testing webhooks locally. (Other tools likelocaltunnel
or cloud-specific tunnels exist. Note that freengrok
URLs are temporary and change each time you restartngrok
_ making them unsuitable for permanent webhook configurations in MessageBird; use them only for active development sessions.)
Expected Outcome:
A functional Next.js application capable of sending text and template messages via MessageBird's WhatsApp API and receiving incoming messages through a configured webhook. This guide provides a functional starting point. While it includes advice and patterns for production (like security checks_ logging suggestions)_ the core code examples use basic constructs (like console.log
) and include placeholders (// TODO:
) that require further implementation and refinement for a truly production-ready system.
1. Setting up the Project
Let's initialize our Next.js project and install the necessary dependencies.
1.1 Create Next.js App:
Open your terminal and run the following command:
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).
1.2 Install Dependencies:
We need the MessageBird Node.js SDK and dotenv
for environment variables. If using Prisma_ install it as well.
npm install messagebird dotenv
# Optional: Install Prisma for database logging
npm install prisma @prisma/client
# Optional: Initialize Prisma (choose your database provider)
npx prisma init --datasource-provider postgresql # or sqlite_ mysql_ etc.
1.3 Configure Environment Variables:
Create a file named .env.local
in the root of your project. This file stores sensitive credentials and configuration. Never commit this file to version control.
# .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
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. Be extremely careful about using Live keys in development environments; prefer Test keys where possible during development.MESSAGEBIRD_CHANNEL_ID
: Identifies the specific WhatsApp channel you configured in MessageBird.MESSAGEBIRD_WEBHOOK_SECRET
: Used to verify that incoming webhook requests genuinely originate from MessageBird.DATABASE_URL
: Connection string for Prisma if you are 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.
1.4 Project Structure:
Your relevant project structure should look similar to this:
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.
// 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.
2. Implementing Core Functionality (Sending Messages)
Let's create the API endpoint responsible for sending outbound WhatsApp messages.
2.1 Create the API Route:
Create the file src/app/api/send-whatsapp/route.ts
.
// 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)
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 'your_messagebird_template_namespace' with your actual MessageBird template namespace!
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."" });
}
Explanation:
- Import Dependencies: Import
NextRequest
,NextResponse
, the initializedmbClient
, and MessageBird types. - Define Request Body: An interface
SendMessageRequestBody
defines the expected structure of incoming POST requests. Using a validation library like Zod is highly recommended for production. - POST Handler: This asynchronous function handles POST requests.
- Parse & Validate: It parses the JSON body and performs basic validation to ensure the recipient (
to
) and message content (text
ortemplate
) are present. It also checks for theMESSAGEBIRD_CHANNEL_ID
. - Construct Parameters: It builds the
params
object required by themessagebird.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: { ... } }
.
- Template Handling: If
body.template
exists, it setstype
tohsm
and constructs thehsm
object.namespace
: Crucial: You must 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.
- Text Handling: If
body.text
exists, it setstype
totext
andcontent
accordingly. - API Call: It uses
mbClient.messages.create
to send the message. (Note: This example wraps the callback-based SDK method in aPromise
for use withasync/await
. Check the documentation for the specific version of themessagebird
SDK you are using; newer versions might offer direct Promise support, which could simplify this code.) - 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. - Response: Returns a JSON response indicating success (with message ID) or failure.
- Logging: Uses
console.log
/console.error
for simplicity in this example. For production, replace these with a structured logger (see Section 5.2).
3. Building the API Layer (Receiving Messages/Webhook)
Now, let's create the endpoint that MessageBird will call whenever your WhatsApp number receives a message or event.
3.1 Create the Webhook API Route:
Create the file src/app/api/messagebird-webhook/route.ts
.
// 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> {
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
const signedPayload = `${timestamp}.${webhookSecret}.${bodyText}`;
// Calculate the expected signature
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(signedPayload)
.digest('hex');
// Compare signatures securely
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
// It's crucial to 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." });
}
Explanation:
- Import Dependencies: Includes
crypto
for signature verification and optionalprisma
. verifySignature
Function:- Retrieves the
messagebird-signature
,messagebird-request-timestamp
headers, and yourMESSAGEBIRD_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.
- Retrieves the
- POST Handler:
- Verify Signature: Calls
verifySignature
immediately. If verification fails, it returns a401 Unauthorized
response and stops processing. This is critical for security. - Parse Payload: Only after verification succeeds, it parses the JSON payload.
- Process Event: Uses a
switch
statement onpayload.type
to handle different events.message.created
/message.updated
: Logs details about the message, including direction (mo
for incoming,mt
for outgoing) and status.- Incoming Message Logic (
mo
): Extracts sender (originator
), content type, and content. Includes commented-out Prisma code for logging the message to a database (see Section 6, which is not present in this document but referenced). This// TODO:
block is a critical placeholder where you need to implement your specific business logic for responding to or acting upon received messages. - Outgoing Status Update Logic (
mt
): Logs status updates for messages you sent. Includes commented-out Prisma code for updating the status in the database.
- Acknowledge Receipt: Returns 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: Catches errors during payload processing and logs them. It's generally best practice to still return
200 OK
for non-critical processing errors to avoid webhook retries, but return500
if the error prevents any meaningful processing. - Logging: Uses
console.log
/console.error
/console.warn
for simplicity. For production, replace these with a structured logger (see Section 5.2).
- Verify Signature: Calls
4. Integrating with MessageBird (Dashboard Configuration)
Now, connect your Next.js application endpoints to your MessageBird account.
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 forMESSAGEBIRD_API_KEY
.
4.2 Obtain WhatsApp Channel ID:
- In the MessageBird Dashboard, go to Channels.
- Select your configured WhatsApp channel.
- Find the Channel ID (it's usually a long alphanumeric string).
- Paste this value into
MESSAGEBIRD_CHANNEL_ID
in your.env.local
.
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:
- Start your Next.js app:
npm run dev
- Use
ngrok
to expose your local port (default 3000):ngrok http 3000
- Copy the
https
forwarding URL provided byngrok
(e.g.,https://a1b2-c3d4-e5f6.ngrok-free.app
). - Use this
ngrok
URL in MessageBird:https://a1b2-c3d4-e5f6.ngrok-free.app/api/messagebird-webhook
. Remember that freengrok
URLs are temporary and will change if you restartngrok
, requiring you to update the webhook URL in MessageBird frequently during development.
- Start your Next.js app:
- Production: Use your deployed application's URL:
- 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
andmessage.updated
. - Save the webhook configuration in MessageBird.
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. Implementing Error Handling, Logging, and Retry Mechanisms
Robust error handling and logging are essential for production systems.
5.1 Error Handling Strategy:
- API Routes: Use
try...catch
blocks extensively in bothsend-whatsapp
andmessagebird-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 a200 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.
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.
// 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, you should replace these with structured logging using a library like Pino, as demonstrated here.