code examples
code examples
Build a WhatsApp Bot with Vonage Messages API, Node.js, and Express: Complete Tutorial
Complete guide to building a WhatsApp messaging application using Vonage Messages API and Node.js. Learn webhook setup, authentication, sandbox testing, and production deployment with code examples.
Build a WhatsApp Bot with Vonage Messages API and Node.js
This complete tutorial shows you how to build a Node.js application using the Express framework to send and receive WhatsApp messages via the Vonage Messages API. You'll learn everything from initial project setup through webhook configuration, authentication, and deployment considerations.
Note: This article covers the Vonage Messages API integration. The filename references Infobip, but the content focuses exclusively on Vonage's WhatsApp implementation.
This application enables two-way WhatsApp communication, allowing your Node.js service to react to incoming messages and send automated replies. It solves the need for programmatic interaction with users on the world's most popular messaging platform, handling over 100 billion messages daily.
Technologies Used:
- Node.js: JavaScript runtime for building server-side applications (v20 LTS or v22 LTS recommended – v20 supported until mid-2026, v22 until April 2027)
- Express: Minimal and flexible Node.js web application framework (v5.1.0+ recommended with official LTS support)
- Vonage Messages API: Unified API for sending and receiving messages across WhatsApp, SMS, MMS, and more
- Vonage Node SDK: Simplifies interaction with Vonage APIs (v1.20.3 as of 2025)
- ngrok: Exposes your local development server to the internet for webhook testing (Free plan: 1 GB/month data transfer, 2-hour session limit, 1 simultaneous tunnel)
- dotenv: Loads environment variables from a
.envfile intoprocess.env
System Architecture:
The communication flow involves the user, WhatsApp, Vonage, your application (via ngrok during development), and potentially logs or a database:
- A WhatsApp user sends a message.
- WhatsApp forwards the message to the Vonage platform.
- Vonage sends an inbound message webhook to your ngrok tunnel URL.
- ngrok forwards the webhook to your local Node.js/Express application.
- Your application processes the message (e.g., logs it, stores it in a DB).
- Your application uses the Vonage SDK to send a reply message back to Vonage.
- Vonage sends the reply message to WhatsApp.
- WhatsApp delivers the reply to the original user.
- Vonage also sends message status webhooks (e.g., 'delivered', 'read') to your ngrok tunnel URL.
- ngrok forwards the status webhook to your application.
- Your application processes the status update.
Prerequisites:
- A Vonage API account. Sign up here if you don't have one.
- Node.js and npm (or yarn) installed (v20 LTS or higher recommended). Verify with
node -vandnpm -v. - ngrok installed and authenticated. Download and set up ngrok here.
- A WhatsApp-enabled mobile device for testing.
Final Outcome:
By the end of this guide, you will have a running Node.js Express application capable of:
- Receiving incoming WhatsApp messages sent to your Vonage Sandbox number.
- Automatically sending a predefined reply ("Message received.") back to the sender via WhatsApp.
- Receiving and logging message status updates (e.g., delivered, read).
- Verifying webhook signatures to ensure requests originate from Vonage.
1. Set Up Your Node.js Project
Initialize your Node.js project and set up the basic structure.
-
Create Project Directory: Open your terminal or command prompt and create a new directory for your project, then navigate into it.
bashmkdir vonage-whatsapp-guide cd vonage-whatsapp-guide -
Initialize Node.js Project: Initialize the project using npm. The
-yflag accepts default settings.bashnpm init -yThis creates a
package.jsonfile. -
Create
.gitignore: Create a.gitignorefile to prevent sensitive information and unnecessary files from being committed to version control.bashtouch .gitignoreAdd the following lines to
.gitignore:Code# Dependencies node_modules/ # Environment variables .env # Optional Editor/OS files .DS_Store *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Vonage Private Key (if stored in project) private.key *.keyWhy
.gitignore? It's crucial for security (keeping.envand keys out of Git) and keeps your repository clean by excluding generated files likenode_modules. -
Create Project Files: Create the main application file and the environment configuration file.
bashmkdir src touch src/server.js touch .envYour project structure should now look like this:
textvonage-whatsapp-guide/ ├── src/ │ └── server.js ├── .env ├── .gitignore └── package.json
2. Configure Environment Variables
Use a .env file to store sensitive credentials and configuration settings.
-
Populate
.env: Open the.envfile and add the following variable keys. We will fill in the values in the next steps.Code# Vonage API Credentials & Application Settings VONAGE_API_KEY= VONAGE_API_SECRET= VONAGE_APPLICATION_ID= VONAGE_PRIVATE_KEY= # Path to your private key file VONAGE_API_SIGNATURE_SECRET= # Vonage WhatsApp Sandbox Number (or your purchased number for production) VONAGE_WHATSAPP_NUMBER=14157386102 # Default Sandbox Number # Server Configuration PORT=8000 -
Obtain Vonage Credentials:
VONAGE_API_KEY&VONAGE_API_SECRET:- Log in to your Vonage API Dashboard.
- Your API Key and Secret are displayed prominently on the main dashboard page.
- Copy and paste these values into your
.envfile.
VONAGE_API_SIGNATURE_SECRET:- Navigate to Dashboard Settings.
- Find the ""API secrets"" section and click ""Edit"" or ""Add new secret"".
- Copy an existing Signature Secret or generate a new one.
- Paste this value into
VONAGE_API_SIGNATURE_SECRETin your.envfile. This is used to verify webhook signatures.
VONAGE_APPLICATION_ID&VONAGE_PRIVATE_KEY: These will be generated in the next step when creating the Vonage Application.VONAGE_WHATSAPP_NUMBER: For this tutorial using the sandbox, the number is pre-filled (14157386102). If transitioning to production with a purchased Vonage number enabled for WhatsApp, replace this with that number (in E.164 format without leading+or00, e.g.,447700900000).PORT: The local port your Express server will listen on.8000is a common choice, but you can change it if needed.
3. Set Up Your Vonage Application
A Vonage Application acts as a container for your communication settings, linking phone numbers and webhooks.
-
Navigate to Applications: In the Vonage API Dashboard, go to "Applications" under the "Build & Manage" section in the left-hand menu. Click "Create a new application".
-
Configure Application:
- Name: Give your application a descriptive name (e.g., "Node WhatsApp Guide App").
- Generate Public and Private Key: Click the "Generate public and private key" button. Immediately save the
private.keyfile that downloads. Move this file into the root of yourvonage-whatsapp-guideproject directory.- Security Note: Treat this
private.keyfile like a password. Do not commit it to version control (it should be in your.gitignore). For production, consider more secure key management strategies.
- Security Note: Treat this
- Update
.env: Set theVONAGE_PRIVATE_KEYvariable in your.envfile to the path of the downloaded key (e.g.,./private.keyif it's in the root).Code# Example assuming private.key is in the project root VONAGE_PRIVATE_KEY=./private.key - Capabilities: Find the "Capabilities" section and toggle "Messages" ON.
- Webhooks (Placeholders for now): You'll see fields for "Inbound URL" and "Status URL". We need ngrok running to get the actual URLs. For now, you can leave them blank or enter temporary placeholders like
http://localhost:8000/inboundandhttp://localhost:8000/status. We will update these later. - Save Application: Scroll down and click "Generate new application".
-
Get Application ID: After saving, you'll be taken to the application's details page. Copy the Application ID.
- Update
.env: Paste the copied ID into theVONAGE_APPLICATION_IDfield in your.envfile.
- Update
-
Link Number (Optional but Recommended for Production): If you intend to use a specific Vonage number you've purchased (required for production WhatsApp Business API usage), scroll down to the "Linked numbers" section on the application details page and link your desired number. For the sandbox, this step isn't strictly necessary as we use the shared sandbox number.
4. Configure the WhatsApp Sandbox for Testing
The Vonage WhatsApp Sandbox provides a testing environment without needing a fully approved WhatsApp Business Account initially. Sandbox limitations: 1 message per second, 100 messages per month across all channels (as of 2024).
-
Navigate to Sandbox: In the Vonage API Dashboard, go to ""Messages API Sandbox"" under the ""Build & Manage"" section.
-
Activate Sandbox: Follow the on-screen instructions. This usually involves:
- Scanning a QR code with your WhatsApp mobile app OR
- Sending a specific message from your WhatsApp number to the Vonage Sandbox number displayed (+14157386102).
- This whitelists your personal WhatsApp number to interact with the sandbox. Only whitelisted numbers can send messages to the sandbox.
- Important: The sandbox number is shared among all Vonage sandbox users and has strict rate limits.
-
Configure Sandbox Webhooks: Find the ""Webhooks"" section on the Sandbox page. Like the application webhooks, we need ngrok running first. Leave these blank for now or use temporary placeholders. We'll update them shortly.
5. Set Up ngrok and Configure Webhooks
Vonage needs a publicly accessible URL to send webhook events (incoming messages, status updates) to your local machine. ngrok creates a secure tunnel for this.
-
Start ngrok: Open a new terminal window (keep the first one for running the Node app later). Run ngrok, telling it to forward traffic to the port your Express server will use (defined as
PORTin.env, default8000).bashngrok http 8000 -
Copy ngrok URL: ngrok will display session information, including a "Forwarding" URL that looks like
https://<random-string>.ngrok-free.app(or similar, depending on your ngrok plan). Copy this HTTPS URL. This is your public base URL. Note: Free ngrok plans generate a new random URL each time you restart it. You'll need to update your Vonage webhooks whenever this URL changes. Paid plans offer stable subdomains. Free plan limitations: 1GB data transfer/month, 2-hour maximum session duration, 1 simultaneous tunnel, interstitial warning page on browser traffic. -
Update Vonage Application Webhooks:
- Go back to your Vonage Application settings (Dashboard > Applications > Your App Name).
- Under "Capabilities" > "Messages":
- Set Inbound URL:
YOUR_NGROK_HTTPS_URL/inbound(e.g.,https://<random-string>.ngrok-free.app/inbound) - Set Status URL:
YOUR_NGROK_HTTPS_URL/status(e.g.,https://<random-string>.ngrok-free.app/status)
- Set Inbound URL:
- Click "Save changes".
-
Update Vonage Sandbox Webhooks:
- Go back to the "Messages API Sandbox" page in the dashboard.
- Under the "Webhooks" section:
- Set Inbound Message URL:
YOUR_NGROK_HTTPS_URL/inbound - Set Message Status URL:
YOUR_NGROK_HTTPS_URL/status
- Set Inbound Message URL:
- Click "Save webhooks".
Why two sets of webhooks? The Application webhooks are the general configuration. The Sandbox webhooks specifically override these for sandbox traffic only, allowing you to test without affecting potential production configurations on the same application.
6. Install Required Dependencies
Install the necessary npm packages for your application.
-
Run Install Command: In your project's terminal window (the one in the
vonage-whatsapp-guidedirectory), run:bashnpm install @vonage/server-sdk @vonage/messages @vonage/jwt express dotenv@vonage/server-sdk: The main Vonage SDK for Node.js, includes authentication and core functionalities.@vonage/messages: Specific helpers for constructing message objects for the Messages API (likeWhatsAppText).@vonage/jwt: Used for verifying the signature of incoming webhook requests.express: The web framework.dotenv: To load variables from the.envfile.
This will install the packages and add them to your
package.jsonandpackage-lock.json.
7. Implement Core Functionality (src/server.js)
Now, write the code for your Express server to handle incoming messages and send replies.
Open src/server.js and add the following code, section by section:
-
Import Dependencies and Initialize Express:
javascript// src/server.js require('dotenv').config(); // Load environment variables from .env file first const express = require('express'); const { Vonage } = require('@vonage/server-sdk'); const { WhatsAppText } = require('@vonage/messages'); const { verifySignature } = require('@vonage/jwt'); const app = express(); // Use Express's built-in JSON body parser app.use(express.json()); // Use Express's built-in URL-encoded body parser (optional, but good practice) app.use(express.urlencoded({ extended: true })); const PORT = process.env.PORT || 8000; // Use port from .env or default to 8000Explanation: We load environment variables immediately, import necessary modules, initialize the Express app, and configure middleware to parse incoming JSON and URL-encoded request bodies. We also define the port.
-
Initialize Vonage Client:
javascript// Initialize Vonage Client with credentials from environment variables const vonage = new Vonage( { apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET, applicationId: process.env.VONAGE_APPLICATION_ID, privateKey: process.env.VONAGE_PRIVATE_KEY, // Path to your private key file }, { // Use the API host for the Messages API Sandbox // For production, remove this option to use the standard API endpoint apiHost: 'https://messages-sandbox.nexmo.com', } );Explanation: We create an instance of the Vonage SDK, passing our credentials read securely from
process.env. TheapiHostoption directs requests to the sandbox environment. Remember to removeapiHostwhen moving to production. -
Implement JWT Signature Verification:
javascript// Function to verify JWT signature on incoming status webhooks const verifyVonageSignature = (req, res, next) => { try { // Extract token from 'Authorization: Bearer <token>' header const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { console.error('Error: Missing or invalid Authorization header'); return res.status(401).send('Unauthorized: Missing bearer token'); } const token = authHeader.split(' ')[1]; // Verify the signature using the token and your signature secret if (!verifySignature(token, process.env.VONAGE_API_SIGNATURE_SECRET)) { console.error('Error: Invalid JWT signature'); return res.status(401).send('Unauthorized: Invalid signature'); } console.log('JWT Signature Verified'); // If signature is valid, proceed to the next middleware/route handler next(); } catch (error) { console.error('Error verifying JWT:', error.message); res.status(401).send('Unauthorized: Signature verification failed'); } };Explanation: This middleware function checks the
Authorizationheader of incoming requests (specifically for the/statuswebhook). It extracts the JWT, verifies its signature against theVONAGE_API_SIGNATURE_SECRETfrom your.env. If verification fails or the header is missing, it sends a401 Unauthorizedresponse. If successful,next()passes control to the actual route handler. This ensures that status updates genuinely come from Vonage. -
Implement Message Sending Function:
javascript// Function to send a WhatsApp message using Vonage Messages API const sendWhatsAppReply = async (recipientNumber) => { const senderNumber = process.env.VONAGE_WHATSAPP_NUMBER; const messageText = 'Message received.'; // The reply message console.log(`Attempting to send reply to ${recipientNumber} from ${senderNumber}`); try { // Construct the message object using WhatsAppText helper const message = new WhatsAppText({ text: messageText, to: recipientNumber, from: senderNumber, }); // Send the message using the Vonage client const response = await vonage.messages.send(message); console.log(`Message sent successfully with UUID: ${response.messageUuid}`); return response; // Return the response object } catch (error) { // Log detailed error information console.error('Error sending WhatsApp message:'); if (error.response) { // Error from Vonage API (e.g., invalid number, authentication) console.error(' Status:', error.response.status); console.error(' Headers:', JSON.stringify(error.response.headers, null, 2)); console.error(' Body:', JSON.stringify(error.response.data, null, 2)); } else if (error.request) { // Request was made but no response received console.error(' Request Error:', error.request); } else { // Setup error or other issue console.error(' General Error:', error.message); } // Re-throw the error if needed for higher-level handling, or handle appropriately throw error; // Or return null, depending on desired behavior } };Explanation: This asynchronous function takes the recipient's number, defines the sender (from
.env) and the message text. It uses the@vonage/messageshelper (WhatsAppText) to structure the payload correctly for thevonage.messages.sendmethod. Detailed error logging is included to help diagnose issues during sending. -
Implement Inbound Message Webhook:
javascript// Route to handle incoming WhatsApp messages from Vonage app.post('/inbound', async (req, res) => { console.log('Received inbound message:'); console.log(JSON.stringify(req.body, null, 2)); // Log the full incoming request body const { from: senderNumber, message } = req.body; // Basic validation: Check if sender number is present if (!senderNumber || !senderNumber.number) { console.error('Error: Invalid inbound payload - missing sender number.'); // Respond 400 Bad Request, Vonage might retry but unlikely to succeed return res.status(400).send('Bad Request: Missing sender number.'); } // Basic validation: Check if message content is present if (!message || !message.content || !message.content.text) { // Could be an image, audio, etc. Handle based on message.content.type console.log('Received non-text message type:', message?.content?.type); // Decide how to handle non-text messages - for now, just acknowledge. return res.status(200).send('OK'); // Acknowledge receipt even if not processing text } console.log(`Message received from ${senderNumber.number}: "${message.content.text}"`); // Adjusted quoting for log clarity try { // Send the predefined reply back to the sender await sendWhatsAppReply(senderNumber.number); // Send a 200 OK response to Vonage to acknowledge receipt res.status(200).send('OK'); } catch (error) { console.error('Error processing inbound message or sending reply:', error.message); // Send a 500 Internal Server Error if sending reply fails res.status(500).send('Internal Server Error'); } });Explanation: This defines the
/inboundroute that Vonage calls when a message arrives. It logs the incoming payload, extracts the sender's number (from.number), validates basic payload structure, logs the message text, callssendWhatsAppReplyto send the response, and crucially, sends a200 OKstatus back to Vonage. If you don't send a 200 OK, Vonage will assume delivery failed and may retry the webhook. -
Implement Status Update Webhook:
javascript// Route to handle message status updates from Vonage // Apply the JWT verification middleware *before* the route handler app.post('/status', verifyVonageSignature, (req, res) => { console.log('Received status update:'); console.log(JSON.stringify(req.body, null, 2)); // Log the full status update body // Extract relevant info (e.g., message UUID, status, timestamp) const { message_uuid, status, timestamp, to } = req.body; console.log(`Status for message ${message_uuid} to ${to?.number}: ${status} at ${timestamp}`); // You can add logic here to update message status in a database, etc. // For example: updateDatabase(message_uuid, status); // Send a 200 OK response to Vonage to acknowledge receipt res.status(200).send('OK'); });Explanation: This defines the
/statusroute. Importantly, we apply theverifyVonageSignaturemiddleware before the main handler function. This ensures only authenticated requests from Vonage are processed. The handler logs the status update details (likedelivered,read) and responds with200 OK. -
Start the Server:
javascript// Start the Express server app.listen(PORT, () => { console.log(`Server is listening on http://localhost:${PORT}`); console.log('Ensure ngrok is running and webhooks are configured correctly.'); });Explanation: This starts the Express server, making it listen for incoming connections on the specified
PORT.
8. Build the API Endpoints
The API layer in this application consists of the two webhook endpoints we created:
-
POST /inbound: Receives incoming WhatsApp messages from Vonage.- Authentication: None required from the sender (WhatsApp user), but Vonage handles the connection.
- Request Body (JSON Example):
json
{ ""from"": { ""type"": ""whatsapp"", ""number"": ""447700900001"" }, ""to"": { ""type"": ""whatsapp"", ""number"": ""14157386102"" }, ""message_uuid"": ""a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8"", ""message"": { ""content"": { ""type"": ""text"", ""text"": ""Hello Server!"" }, ""whatsapp"": { /* WhatsApp specific metadata */ } }, ""timestamp"": ""2025-04-20T10:30:00.123Z"" } - Response:
200 OK: Successfully received and processing initiated.400 Bad Request: Payload missing essential information (e.g., sender number).500 Internal Server Error: Error occurred during processing (e.g., failed to send reply).
- Testing: Not directly testable with
curlas it requires Vonage to initiate the POST. Testing is done by sending a WhatsApp message from your whitelisted number to the Sandbox number.
-
POST /status: Receives message status updates from Vonage (e.g., delivered, read).- Authentication: Requires a valid JWT in the
Authorization: Bearer <token>header, verified usingVONAGE_API_SIGNATURE_SECRET. - Request Body (JSON Example - Delivered):
json
{ ""message_uuid"": ""b2c3d4e5-f6g7-8901-h2i3-j4k5l6m7n8o9"", ""to"": { ""type"": ""whatsapp"", ""number"": ""447700900001"" }, ""from"": { ""type"": ""whatsapp"", ""number"": ""14157386102"" }, ""timestamp"": ""2025-04-20T10:30:05.456Z"", ""status"": ""delivered"", ""usage"": { ""currency"": ""EUR"", ""price"": ""0.0063"" }, ""client_ref"": ""optional-client-reference"" } - Response:
200 OK: Successfully received status update.401 Unauthorized: Missing or invalid JWT signature.
- Testing: You can't easily generate a valid test JWT without mimicking Vonage's signing process. Testing relies on observing status updates after sending messages via the application.
- Authentication: Requires a valid JWT in the
9. Integrate with Vonage Messages API
Integration points are:
- SDK Initialization: The
Vonageclient is initialized using API Key, Secret, Application ID, and Private Key from.env. - Sending Messages: The
vonage.messages.send()method uses the initialized client to interact with the Vonage Messages API. - Receiving Webhooks: The Express server listens on
/inboundand/statusendpoints configured in the Vonage Application and Sandbox settings, using ngrok for tunneling during development. - Webhook Security: The
/statuswebhook uses JWT signature verification (@vonage/jwtandVONAGE_API_SIGNATURE_SECRET) to confirm authenticity.
Secure Handling of Credentials:
- All secrets (API Key, Secret, Signature Secret, Private Key path) are stored in the
.envfile. - The
.envfile is included in.gitignoreto prevent accidental commits. - The
dotenvpackage loads these variables intoprocess.env, avoiding hardcoding them in the source code. - The
private.keyfile itself is also in.gitignore.
10. Implement Error Handling and Logging
- Error Handling Strategy:
- Use
try...catchblocks around asynchronous operations (likevonage.messages.send). - Log errors using
console.errorwith detailed information (status code, body for API errors). - Send appropriate HTTP status codes back to Vonage (e.g.,
500if reply sending fails in/inbound,401for failed auth in/status).
- Use
- Logging:
- Current implementation uses
console.logandconsole.error. - Production Recommendation: Use a dedicated logging library like Pino or Winston for structured logging (JSON format), different log levels (info, warn, error), and configurable output streams (e.g., file, external logging service).
javascript
// Example using Pino (after npm install pino) // const pino = require('pino')(); // pino.info('Server started'); // pino.error(error, 'Failed to send message');
- Current implementation uses
- Retry Mechanisms:
- Vonage Webhook Retries: If your
/inboundor/statusendpoint doesn't return a200 OKwithin a timeout period, Vonage will automatically retry sending the webhook several times with increasing delays. Ensure your endpoints respond promptly. - Application Send Retries: The current
sendWhatsAppReplyfunction doesn't implement retries if the initial send to Vonage fails. For production, you might add a retry mechanism (e.g., using a library like async-retry) with exponential backoff for transient network errors or temporary Vonage API issues when callingvonage.messages.send.
- Vonage Webhook Retries: If your
11. Add Database Integration (Optional Extension)
This basic guide doesn't store messages. For persistence, you'd add a database.
- Potential Schema (Conceptual):
Messagestable:message_uuid(TEXT, PRIMARY KEY) - From Vonage response/webhooksvonage_sender(TEXT)vonage_recipient(TEXT)content_text(TEXT, nullable)content_type(TEXT) - e.g., 'text', 'image'direction(TEXT) - 'inbound' or 'outbound'status(TEXT) - 'submitted', 'delivered', 'read', 'failed', 'rejected'vonage_timestamp(TIMESTAMP WITH TIME ZONE)created_at(TIMESTAMP WITH TIME ZONE, default NOW())updated_at(TIMESTAMP WITH TIME ZONE)
- Implementation:
- Choose a database (PostgreSQL, MySQL, MongoDB).
- Use an ORM/Query Builder like Prisma, Sequelize, or TypeORM.
- Data Access: Create functions to insert messages on
/inboundand outbound sends, and update status on/statuswebhooks using themessage_uuid. - Migrations: Use the ORM's migration tools (
prisma migrate dev,sequelize db:migrate) to manage schema changes.
12. Security Best Practices
- Webhook Signature Verification: Already implemented for the
/statuswebhook using JWT. Crucial to prevent spoofed status updates. While/inbounddoesn't typically use JWT, you could implement IP whitelisting for Vonage webhook IPs in production (though IPs can change). - Input Validation:
- Basic checks added in
/inboundforsenderNumber. - Recommendation: Use a library like Joi or express-validator for more robust validation of incoming webhook payloads (
req.body).
- Basic checks added in
- Rate Limiting:
- Protect against abuse or accidental loops. Use middleware like express-rate-limit.
javascript
// Example (install first: npm install express-rate-limit) // const rateLimit = require('express-rate-limit'); // const limiter = rateLimit({ // windowMs: 15 * 60 * 1000, // 15 minutes // max: 100 // limit each IP to 100 requests per windowMs // }); // app.use(limiter); // Apply to all requests // app.use('/inbound', specificLimiter); // Apply differently per route
- Protect against abuse or accidental loops. Use middleware like express-rate-limit.
- Secure Headers: Use middleware like Helmet to set various HTTP headers that improve security (XSS protection, frameguard, etc.).
bash
npm install helmetjavascript// const helmet = require('helmet'); // app.use(helmet()); - Dependency Management: Regularly update dependencies (
npm update) and use tools likenpm auditto check for known vulnerabilities.
13. Handle Edge Cases and Production Requirements
- Non-Text Messages: The current
/inboundhandler only processesmessage.content.type === 'text'. You need to add logic to handle other types (image,audio,video,file,location, etc.) based onreq.body.message.content.typeand the corresponding content structure if required. - Sandbox Limitations:
- Only whitelisted numbers can initiate conversations with the sandbox number.
- The sandbox number (
14157386102) is shared and has limitations. - Rate limits: 1 message per second, 100 messages per month total (as of 2024).
- Templates are not typically tested in the sandbox.
- Production (WhatsApp Business API - WABA):
- Requires a purchased Vonage number enabled for WhatsApp and Facebook Business Manager approval.
- 24-Hour Window Rule (Updated June 2025):
- Within 24 hours of the last user-initiated message, you can send free-form service messages at no charge.
- Each new customer message resets the 24-hour timer.
- Starting April 1, 2025: Utility templates sent within the 24-hour window are also free.
- Outside this window, you must use pre-approved Message Templates (Marketing, Utility, or Authentication categories).
- Pricing Model (June 2025 Update): WhatsApp shifted from conversation-based billing to per-message pricing. Charges apply per delivered message, varying by category and market.
- 72-Hour Free Entry Window: If a user messages from a Click-to-WhatsApp ad or Facebook Page CTA, and you respond within 24 hours, all messages (any category) are free for 72 hours.
- Templates: Require approval by Meta/WhatsApp. The SDK supports sending templates (
vonage.messages.send(new WhatsAppTemplate(...))). - Messaging Limits: Business phone numbers start at 250 delivered messages per 24-hour period (outside customer service windows), scaling to higher tiers (1,000, 10,000, 100,000+) based on quality ratings and volume.
- Rate Limits: Both Vonage and WhatsApp enforce rate limits. Implement appropriate error handling and potential backoff/retry logic if you hit these limits (
429 Too Many Requestserrors).
14. Optimize Performance for Production
For this simple application, performance is unlikely to be an issue initially. For high-volume scenarios:
- Node.js Clustering: Use Node.js's built-in
clustermodule or a process manager like PM2 in cluster mode to utilize multiple CPU cores. - Asynchronous Operations: Ensure all I/O (like database calls, API requests) is non-blocking (using
async/await, Promises). The current code already does this. - Payload Size: Be mindful of large request/response bodies if adding complex features.
- Load Testing: Use tools like k6, Artillery, or ApacheBench to simulate traffic to your webhook endpoints (especially
/inbound) and identify bottlenecks.
Frequently Asked Questions
How do I send WhatsApp messages from Node.js?
Use the Vonage Messages API with the @vonage/server-sdk and @vonage/messages npm packages. Initialize the Vonage client with your API credentials, create a WhatsAppText message object, and call vonage.messages.send(). This guide provides complete code examples for implementing WhatsApp messaging in Node.js.
What are the Vonage WhatsApp Sandbox limitations?
The Vonage Messages API Sandbox has the following limitations: 1 message per second rate limit, 100 messages per month across all channels, only whitelisted phone numbers can send messages to the sandbox, and the sandbox number (+14157386102) is shared among all users. For production use, you need a dedicated WhatsApp Business Account.
Do I need a WhatsApp Business Account to test?
No, you can use the Vonage Messages API Sandbox for testing without a WhatsApp Business Account. Simply whitelist your phone number by scanning a QR code or sending a message to the sandbox number. For production deployment, you'll need an approved WhatsApp Business Account through Facebook Business Manager.
What is the WhatsApp 24-hour messaging window?
The 24-hour messaging window allows you to send free-form service messages to users for 24 hours after they last message you. Each new message from the user resets the timer. Outside this window, you must use pre-approved Message Templates. Starting April 1, 2025, utility templates sent within the 24-hour window are free.
How do I verify Vonage webhook signatures?
Use the @vonage/jwt package's verifySignature() function with your VONAGE_API_SIGNATURE_SECRET. Extract the JWT from the Authorization: Bearer <token> header and verify it before processing status webhooks. This ensures requests genuinely come from Vonage and prevents spoofed webhooks.
What Node.js version should I use for Vonage integration?
Use Node.js v20 LTS (supported until mid-2026) or v22 LTS (supported until April 2027) for production applications. Both versions are actively maintained and compatible with the latest Vonage SDK (v1.20.3). Express v5.1.0+ is recommended with official LTS support.
How do I handle WhatsApp message templates?
WhatsApp message templates require pre-approval from Meta/WhatsApp before use. Templates are required for messaging outside the 24-hour window. Use the Vonage SDK's WhatsAppTemplate class to send template messages. All templates must follow WhatsApp's formatting guidelines and be approved before production use.
What are ngrok alternatives for webhook testing?
While ngrok is popular for webhook testing, alternatives include localtunnel, Hookdeck CLI, and Cloudflare Tunnel. Free ngrok limitations include 1 GB/month data transfer, 2-hour sessions, 1 simultaneous tunnel, and interstitial warning pages. Paid plans offer stable domains and longer sessions.
Frequently Asked Questions
How to send WhatsApp messages with Node.js?
Use the Vonage Messages API and Node.js SDK. After setting up a Vonage application and linking a WhatsApp number, you can send messages programmatically using the `vonage.messages.send()` method with a properly formatted message object (e.g., using `WhatsAppText` from `@vonage/messages`).
What is the Vonage Messages API?
The Vonage Messages API is a unified platform for sending and receiving messages across multiple channels, including WhatsApp, SMS, and MMS. It provides a single interface for interacting with these services, simplifying development and integration.
Why does Vonage use webhooks for WhatsApp?
Webhooks allow your application to receive real-time updates from the Vonage platform. When a user sends a WhatsApp message or there's a status update (like 'delivered'), Vonage sends a webhook request to your server containing this information.
When should I remove the apiHost setting?
The `apiHost` setting in the Vonage SDK configuration directs API calls to the sandbox environment. You should remove this setting when transitioning your application from development/testing to production to use the standard Vonage Messages API endpoint.
Can I receive images via WhatsApp with this setup?
The provided example focuses on text messages. To handle other message types like images, you'll need to enhance the '/inbound' route to process different `message.content.type` values (e.g., 'image') and access the content accordingly within the `req.body.message.content` structure.
How to set up a Node.js WhatsApp server?
Combine Node.js with Express and the Vonage Messages API. Create an Express server, install necessary Vonage SDKs, configure webhooks to receive messages and status updates, and implement message sending logic using the Vonage client.
What is ngrok used for with Vonage?
Ngrok creates a public tunnel to your local development server. This allows Vonage's webhooks to reach your application running locally, which is necessary for testing before deployment.
Why use dotenv in a Node.js project?
Dotenv loads environment variables from a `.env` file into `process.env`. This is a secure way to manage sensitive information like API keys and secrets, keeping them out of your codebase and version control.
How to verify Vonage webhook signatures?
Implement JWT verification using the `@vonage/jwt` package. Extract the token from the 'Authorization' header in the webhook request, then use the `verifySignature` function with your signature secret (`VONAGE_API_SIGNATURE_SECRET`) to ensure its validity.
What is a Vonage Application?
A Vonage Application acts as a container for your communication settings. It links phone numbers, webhooks, and other configurations, allowing you to manage your services through the Vonage platform.
How to reply to incoming WhatsApp messages?
Inside the `/inbound` webhook handler, extract the sender's number and the message content. Use the `sendWhatsAppReply` function (or similar logic) with the `vonage.messages.send` method to send a reply message using the Vonage Messages API.
When to use the WhatsApp Sandbox?
The WhatsApp Sandbox is a testing environment ideal for development and initial testing. It allows you to send and receive WhatsApp messages without needing a fully approved WhatsApp Business Account, but with certain limitations.
What are WhatsApp message status updates?
Status updates inform you of the delivery status of your WhatsApp messages. These updates are sent via webhooks to your `/status` endpoint and include statuses like 'submitted', 'delivered', 'read', or 'failed'.
How does the system architecture for Vonage WhatsApp work?
A WhatsApp user's message is routed via Vonage to your application through webhooks. Your app processes the message and sends a reply back through Vonage, which delivers it to WhatsApp. Status updates also flow through webhooks back to your application.