Vonage WhatsApp API Node.js Integration: Complete Express.js Tutorial [2025]
Learn how to integrate WhatsApp and SMS messaging in Node.js with Vonage Messages API. Step-by-step Express.js tutorial with webhook handlers, JWT authentication, and production deployment guides for building scalable messaging applications.
This comprehensive guide teaches you how to build a production-ready WhatsApp and SMS integration using Node.js, Express.js, and the Vonage Messages API. Whether you're implementing customer notifications, two-factor authentication, or automated messaging workflows, this tutorial covers everything you need.
You'll learn how to send and receive WhatsApp messages with Node.js, implement secure webhook integration with Express.js, handle JWT authentication for API security, manage delivery status callbacks, and deploy your messaging application to production. Perfect for developers building customer communication platforms, chatbots, notification systems, or multi-channel messaging services.
Project Overview and Goals
What You'll Build:
You'll build an Express.js web application that:
Sends outbound SMS messages via API calls or internal functions
Sends outbound WhatsApp messages using the Vonage WhatsApp Sandbox for testing
Receives inbound SMS messages via webhooks configured in the Vonage API Dashboard
Receives inbound WhatsApp messages via the same webhook, handling replies
Receives message status updates via a separate webhook for delivery confirmations
Problem You'll Solve:
This application provides a foundational backend service for integrating programmable SMS and WhatsApp messaging into larger systems, enabling customer communication, notifications, two-factor authentication triggers, or basic chatbots.
Technologies Used:
Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its asynchronous nature, large ecosystem (npm), and popularity.
Express.js: A minimal and flexible Node.js web application framework. Provides robust features for web and mobile applications, ideal for creating webhook handlers and potentially a simple API.
Vonage Messages API: A unified API for sending and receiving messages across multiple channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber). Chosen for its multi-channel support and developer-friendly SDKs.
Vonage Node.js SDK (@vonage/server-sdk, @vonage/messages, @vonage/jwt): Official libraries simplifying interaction with the Vonage APIs and handling security features like JWT verification.
dotenv: A zero-dependency module that loads environment variables from a .env file into process.env. Essential for managing credentials securely.
ngrok: A tool to expose local servers to the internet. Crucial for testing Vonage webhooks during development.
Note: For production deployments, ngrok is replaced by a publicly accessible server with a static IP or domain name.
@vonage/messages: Specific SDK for the Messages API.
@vonage/jwt: For verifying webhook signatures.
dotenv: For loading environment variables.
Project Structure:
Your basic project structure should look like this:
Text
vonage-messaging-app/
├── node_modules/
├── package.json
├── package-lock.json
├── .env # Will be created in step 6
├── .gitignore # Will be created in step 7
├── server.js # Will be created in step 8
└── private.key # Will be downloaded from Vonage
Configure Vonage Account Settings:
Before writing code, ensure your Vonage account is set up correctly:
Under API keys > SMS settings, ensure "Which API do you want to use by default for sending SMS?" is set to Messages API.
Click Save changes. This ensures the correct webhook format is used.
Why? Vonage has older SMS APIs. Setting Messages as default ensures consistency.
Create .env File for Credentials:
Create a file named .env in the root of your project directory. This file will store your sensitive credentials and configuration. Never commit this file to version control.
.env
# .env
# Vonage API Credentials (Find on Vonage API Dashboard homepage)
VONAGE_API_KEY=YOUR_API_KEY
VONAGE_API_SECRET=YOUR_API_SECRET
# Vonage Application Credentials (Generated in next step)
VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID
VONAGE_PRIVATE_KEY=./private.key # Path to your downloaded private key file
# Vonage Numbers (Find/Buy in Vonage API Dashboard -> Numbers)
VONAGE_SMS_NUMBER=YOUR_VONAGE_SMS_NUMBER_IN_E164_FORMAT # E.164 format (e.g., 14155551212)
VONAGE_WHATSAPP_NUMBER=YOUR_VONAGE_WHATSAPP_SANDBOX_NUMBER # e.g., 14157386102 (Sandbox Number)
# Webhook Security (Find in Vonage API Dashboard -> Settings -> API secrets)
VONAGE_API_SIGNATURE_SECRET=YOUR_SIGNATURE_SECRET
# Your Phone Number for Testing (E.164 format)
MY_PHONE_NUMBER=YOUR_PERSONAL_PHONE_NUMBER_IN_E164_FORMAT # E.164 format (e.g., 12015551212)
# Server Configuration
PORT=8000
Replace placeholders like YOUR_API_KEY with your actual values (except VONAGE_APPLICATION_ID and VONAGE_PRIVATE_KEY, which we'll get next).
Number Formatting: Enter phone numbers in E.164 format (country code + number, without leading + or 00).
Create .gitignore File:
To prevent accidentally committing sensitive information and unnecessary files, create a .gitignore file in the root directory:
Text
# .gitignore
node_modules
.env
private.key
*.log
Initialize server.js:
Create a file named server.js in the root directory. This will contain our application logic. Add the initial setup code:
Enter an Application name (e.g., "Node Express Messaging App").
Click Generate public and private key. This will automatically download a file named private.key. Save this file in the root of your project directory. The public key is stored securely by Vonage.
Why? This key pair is used for authenticating API requests securely instead of just using the API secret for certain operations.
Important: Copy the Application ID generated on this page and paste it into your .env file for the VONAGE_APPLICATION_ID variable.
Scroll down to Capabilities.
Toggle Messages ON.
You'll see fields for Inbound URL and Status URL. We'll fill these in the next step after starting ngrok. Leave them blank for now or enter temporary placeholders like http://example.com/inbound.
Scroll down to Virtual numbers.
Click Link next to the Vonage phone number you purchased (the one you put in .env as VONAGE_SMS_NUMBER). This connects incoming messages on that number to this application's webhooks.
Click Generate new application.
Start ngrok:
Open a new terminal window/tab (keep the one for running the server separate). Run ngrok to expose the port your Express app will listen on (defined in .env, default is 8000).
Bash
ngrok http 8000
ngrok will display output similar to this:
Text
Session Status online
Account Your Name (Plan: Free)
Version x.y.z
Region United States (us-cal-1)
Latency --
Web Interface http://127.0.0.1:4040
Forwarding https://<YOUR_UNIQUE_ID>.ngrok-free.app -> http://localhost:8000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
Copy the https://<YOUR_UNIQUE_ID>.ngrok-free.app Forwarding URL. This is your public URL for testing.
Configure Webhook URLs in Vonage Application:
Go back to your Vonage Application settings (Vonage API Dashboard -> Click your application name).
Click Edit.
Under Capabilities > Messages:
Set Inbound URL to: YOUR_NGROK_FORWARDING_URL/webhooks/inbound (e.g., https://<YOUR_UNIQUE_ID>.ngrok-free.app/webhooks/inbound)
Set Status URL to: YOUR_NGROK_FORWARDING_URL/webhooks/status (e.g., https://<YOUR_UNIQUE_ID>.ngrok-free.app/webhooks/status)
Why? Inbound URL receives incoming messages. Status URL receives delivery receipts/status updates for outbound messages.
Click Save changes.
Set Up Vonage WhatsApp Sandbox:
The Sandbox allows testing WhatsApp messaging without needing a full WhatsApp Business Account initially.
Navigate to Messages API Sandbox under Build & Manage in the Vonage API Dashboard.
Allowlist Your Number: Follow the instructions to send the specific WhatsApp message shown on the page (e.g., "Allowlist ") from your personal WhatsApp number to the Vonage Sandbox number provided (usually +14157386102). This confirms you consent to receive test messages. The Sandbox number is what you should have in your .env file as VONAGE_WHATSAPP_NUMBER.
Configure Sandbox Webhooks: In the Sandbox settings page, find the Webhooks section.
Set Inbound message URL to: YOUR_NGROK_FORWARDING_URL/webhooks/inbound
Set Message status URL to: YOUR_NGROK_FORWARDING_URL/webhooks/status
Why? These tell the Sandbox where to send incoming WhatsApp messages and status updates, separate from the main application settings but typically pointing to the same place in simple apps.
Under API secrets, find the Signing secret associated with your main API key. Click the eye icon to reveal it.
Copy this secret and paste it into your .env file for VONAGE_API_SIGNATURE_SECRET.
Why? This secret is used to create a signature (JWT) for incoming webhooks, allowing your application to verify they genuinely came from Vonage.
3. Send SMS and WhatsApp Messages with Vonage Messages API
Now, let's add the code to initialize the Vonage client and functions to send SMS and WhatsApp messages.
Initialize Vonage Client:
Add the following code to your server.js file, below the initial require and app setup:
JavaScript
// server.js (Add this section)// --- Vonage Client Initialization ---const vonage = newVonage(
{
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 from .env
},
{
// Use sandbox host for WhatsApp testing if required by SDK version or specific needs// Check Vonage Node SDK documentation for latest recommendations// Often, the SDK handles the correct endpoint automatically based on message type// apiHost: 'https://messages-sandbox.nexmo.com' // Usually only needed for older SDKs or specific sandbox interactions
}
);
This uses the credentials loaded from your .env file to create an authenticated Vonage client instance.
The privateKey path is read directly from the environment variable.
The apiHost override is generally not needed for sending messages with recent SDK versions but is included for awareness if you encounter issues specifically with the sandbox.
Implement sendSms Function:
Add a function to handle sending SMS messages.
JavaScript
// server.js (Add this section)// --- Send Functions ---/**
* Sends an SMS message using the Vonage Messages API.
* @param {string} to - The recipient phone number (E.164 format, e.g., 12015551212).
* @param {string} text - The message content.
*/asyncfunctionsendSms(to, text) {
console.log(`Attempting to send SMS to ${to}...`);
try {
const resp = await vonage.messages.send({
message_type: 'text',
text: text,
to: to,
from: process.env.VONAGE_SMS_NUMBER, // Your Vonage SMS number from .envchannel: 'sms',
});
console.log(`SMS sent successfully to ${to}. Message UUID: ${resp.messageUuid}`);
return resp; // Contains message_uuid
} catch (err) {
console.error(`Error sending SMS to ${to}:`, err?.response?.data || err.message);
// Rethrow or handle specific errors as neededthrow err;
}
}
Uses vonage.messages.send.
Specifies channel: 'sms' and message_type: 'text'.
Uses the VONAGE_SMS_NUMBER from .env as the sender ID.
Includes basic logging and error handling.
Implement sendWhatsApp Function:
Add a function to handle sending WhatsApp messages.
JavaScript
// server.js (Add this section)/**
* Sends a WhatsApp message using the Vonage Messages API.
* Uses the WhatsAppText class for simplicity.
* @param {string} to - The recipient WhatsApp number (E.164 format, e.g., 12015551212).
* @param {string} text - The message content.
*/asyncfunctionsendWhatsApp(to, text) {
console.log(`Attempting to send WhatsApp message to ${to}...`);
try {
// NOTE: For Sandbox, 'from' MUST be the Sandbox number.// For production WhatsApp Business API, 'from' is your provisioned WhatsApp number.const fromNumber = process.env.VONAGE_WHATSAPP_NUMBER;
const resp = await vonage.messages.send(
newWhatsAppText({
text: text,
to: to,
from: fromNumber,
})
);
console.log(`WhatsApp message sent successfully to ${to}. Message UUID: ${resp.messageUuid}`);
return resp; // Contains message_uuid
} catch (err) {
console.error(`Error sending WhatsApp message to ${to}:`, err?.response?.data || err.message);
// Log specific WhatsApp errors if available in err.response.dataif (err.response?.data?.invalid_parameters) {
console.error('Invalid Parameters:', err.response.data.invalid_parameters);
}
throw err;
}
}
Uses vonage.messages.send with a WhatsAppText object from @vonage/messages.
Uses the VONAGE_WHATSAPP_NUMBER (which is the Sandbox number in our setup) as the from number. This is required for the Sandbox.
Includes similar logging and error handling.
(Optional) Add Test Endpoints:
To easily trigger sending messages during development, you can add simple GET endpoints. Remember to remove or secure these properly before production.
JavaScript
// server.js (Add this section - for testing only)// --- Test Endpoints (Development Only - Secure or Remove for Production) ---
app.get('/test/sms', async (req, res) => {
const to = process.env.MY_PHONE_NUMBER; // Send to your own number from .envconst text = `Hello from Vonage SMS! (${newDate().toLocaleTimeString()})`;
if (!to) {
return res.status(400).send('MY_PHONE_NUMBER environment variable not set.');
}
try {
const result = awaitsendSms(to, text);
res.status(200).json({ message: 'SMS test initiated.', result });
} catch (error) {
res.status(500).json({ message: 'Failed to send SMS test.', error: error.message });
}
});
app.get('/test/whatsapp', async (req, res) => {
const to = process.env.MY_PHONE_NUMBER; // Send to your own allowlisted number from .envconst text = `Hello from Vonage WhatsApp Sandbox! (${newDate().toLocaleTimeString()})`;
if (!to) {
return res.status(400).send('MY_PHONE_NUMBER environment variable not set.');
}
try {
// Ensure your number is allowlisted in the Sandbox first!const result = awaitsendWhatsApp(to, text);
res.status(200).json({ message: 'WhatsApp test initiated.', result });
} catch (error) {
res.status(500).json({ message: 'Failed to send WhatsApp test.', error: error.message });
}
});
These endpoints read your personal phone number from .env and call the respective send functions.
4. Build Express.js Webhook Integration for Inbound Messages
Webhooks are HTTP callbacks triggered by Vonage when events occur (like receiving a message or a status update). Our Express app needs endpoints to listen for these callbacks.
Implement JWT Verification Middleware:
Create a middleware function to verify the signature of incoming webhooks. This ensures the request genuinely came from Vonage.
JavaScript
// server.js (Add this section)// --- Webhook Security Middleware ---constverifyVonageSignature = (req, res, next) => {
try {
// Get authorization header (should be "Bearer <JWT>")const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
console.warn('Webhook received without Bearer token.');
return res.status(401).send('Unauthorized: Missing Bearer token.');
}
const token = authHeader.split(' ')[1];
// Verify the JWT using the signature secret from .envconst decoded = verifySignature(token, process.env.VONAGE_API_SIGNATURE_SECRET);
// Optional: Check claims like api_key or application_id if needed// if (decoded.api_key !== process.env.VONAGE_API_KEY) {// console.warn('Webhook received with mismatched API key.');// return res.status(401).send('Unauthorized: Invalid API Key');// }console.log('Webhook JWT verified successfully.');
req.vonage_jwt = decoded; // Attach decoded payload if needed laternext(); // Proceed to the next middleware/handler
} catch (error) {
console.error('Webhook JWT verification failed:', error.message);
res.status(401).send('Unauthorized: Invalid signature.');
}
};
Extracts the JWT from the Authorization: Bearer <token> header.
Uses verifySignature from @vonage/jwt and your VONAGE_API_SIGNATURE_SECRET.
Sends a 401 Unauthorized response if verification fails.
Calls next() to pass control to the actual route handler if verification succeeds.
Create Inbound Webhook Endpoint:
This endpoint (/webhooks/inbound) will receive incoming SMS and WhatsApp messages.
JavaScript
// server.js (Add this section)// --- Webhook Endpoints ---// Endpoint to receive incoming messages (SMS & WhatsApp)// Vonage sends POST requests to this URL
app.post('/webhooks/inbound', verifyVonageSignature, async (req, res) => {
console.log('--- Inbound Webhook Received ---');
console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the full payloadconst message = req.body;
// Basic sanity check for expected propertiesif (!message.from || !message.to || !message.channel) {
console.warn('Received incomplete inbound message data.');
// Still send 200 OK to prevent Vonage retries for malformed requestsreturn res.status(200).end();
}
console.log(`Received message on channel: ${message.channel}`);
console.log(`From: ${message.from.number || message.from.id}`); // Number format varies by channelconsole.log(`To: ${message.to.number || message.to.id}`);
if (message.message_type === 'text') {
console.log(`Text: ${message.text}`);
// --- Example: Auto-reply logic ---if (message.channel === 'whatsapp') {
try {
// Simple reply to incoming WhatsAppconst replyText = `Thanks for your WhatsApp message: "${message.text}"`;
awaitsendWhatsApp(message.from.id, replyText); // Reply to the senderconsole.log(`Sent WhatsApp auto-reply to ${message.from.id}`);
} catch (error) {
console.error(`Failed to send WhatsApp auto-reply to ${message.from.id}`, error);
}
} elseif (message.channel === 'sms') {
// Optional: Implement SMS auto-reply logic here// const replyText = `Thanks for your SMS: "${message.text}"`;// await sendSms(message.from.number, replyText);
}
// --- End Example ---
} else {
console.log(`Received non-text message type: ${message.message_type}`);
// Handle other message types (image, audio, location, etc.) if needed
}
// IMPORTANT: Always respond with 200 OK quickly to acknowledge receipt.// Vonage will retry if it doesn't get a 200 response.
res.status(200).end();
});
Uses the verifyVonageSignature middleware first.
Logs the entire incoming request body (req.body). The structure varies slightly depending on the channel (SMS vs. WhatsApp) and message type. Refer to Vonage Messages API Webhook Reference for details.
Includes a simple example of sending an auto-reply via WhatsApp.
Crucially, always sends a 200 OK response to Vonage to acknowledge receipt, even if processing fails later. This prevents unnecessary retries from Vonage.
Create Status Webhook Endpoint:
This endpoint (/webhooks/status) receives updates about the delivery status of messages you sent.
JavaScript
// server.js (Add this section)// Endpoint to receive message status updates// Vonage sends POST requests to this URL
app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
console.log('--- Status Webhook Received ---');
console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the full payloadconst statusUpdate = req.body;
// Example: Log key status informationif (statusUpdate.message_uuid && statusUpdate.status) {
console.log(`Status Update for ${statusUpdate.message_uuid}: ${statusUpdate.status}`);
if(statusUpdate.error) {
console.error(`Error (${statusUpdate.error.code}): ${statusUpdate.error.reason}`);
}
// You could update a database record here based on message_uuid and status// Statuses include: submitted, delivered, rejected, undeliverable
} else {
console.warn('Received incomplete status update data.');
}
// IMPORTANT: Always respond with 200 OK quickly.
res.status(200).end();
});
Also uses the verifyVonageSignature middleware.
Logs the status update payload. Key fields include message_uuid, status (submitted, delivered, rejected, undeliverable), timestamp, and potentially an error object. See Vonage Messages API Webhook Reference.
Again, always sends a 200 OK response.
5. Test Your WhatsApp and SMS Integration
Now you can run the application and test the integrations.
Ensure ngrok is Running:
Make sure the ngrok http 8000 command is still running in its separate terminal and that the correct ngrok Forwarding URL is configured in your Vonage Application and Sandbox settings.
Start the Node.js Server:
In the terminal window within your project directory, run:
Bash
node server.js
You should see:
Text
Server is listening on port 8000
Ensure ngrok is running and pointing to http://localhost:8000
Test Sending SMS:
Open your web browser and navigate to http://localhost:8000/test/sms.
Check the terminal running node server.js. You should see logs indicating an attempt to send SMS and then success with a message_uuid.
Check your phone (the MY_PHONE_NUMBER specified in .env). You should receive the SMS message shortly.
Check the terminal again. You might see Status Webhook logs related to the SMS delivery (submitted, delivered).
Test Sending WhatsApp:
Important: Ensure your MY_PHONE_NUMBER is allowlisted in the Vonage WhatsApp Sandbox.
Open your web browser and navigate to http://localhost:8000/test/whatsapp.
Check the node server.js terminal for logs indicating the attempt and success.
Check your WhatsApp on your phone. You should receive the message from the Vonage Sandbox number.
Check the terminal for Status Webhook logs related to the WhatsApp message.
Test Receiving SMS:
Using your physical phone, send an SMS message to your VONAGE_SMS_NUMBER.
Check the node server.js terminal. You should see the "Inbound Webhook Received" logs, including the message details (channel: sms, your phone number, the text).
Test Receiving WhatsApp (and Auto-Reply):
Using your physical phone (with the allowlisted WhatsApp number), send a WhatsApp message to the VONAGE_WHATSAPP_NUMBER (the Sandbox number).
Check the node server.js terminal. You should see:
"Inbound Webhook Received" logs (channel: whatsapp, your number, the text).
Logs indicating an attempt to send the WhatsApp auto-reply.
Logs indicating the auto-reply was sent successfully.
Check your WhatsApp. You should receive the auto-reply message from the Sandbox.
Check the terminal for Status Webhook logs related to the auto-reply message you just sent.
6. Handle Errors and Add Logging
The current implementation has basic console.log and console.error. For production:
Use a Dedicated Logger: Integrate a more robust logging library like winston or pino. This enables structured logging (e.g., JSON format), different log levels (debug, info, warn, error), and routing logs to files or external services.
Centralized Error Handling Middleware: Implement an Express error-handling middleware to catch unhandled errors, log them consistently, and return appropriate HTTP responses (e.g., generic 500 errors to the client while logging detailed stack traces).
Specific Vonage Error Codes: The err.response.data object in the catch blocks of the send functions often contains detailed error information from the Vonage API, including specific error codes and descriptions. Log these details and potentially handle specific, retryable errors (like network issues) differently from non-retryable ones (like invalid numbers). Refer to Vonage API Error Codes.
Webhook Reliability: While we send 200 OK immediately, implement background job queues (e.g., using BullMQ with Redis) for processing inbound messages or sending replies if the logic is complex or involves external dependencies. This prevents webhook timeouts and allows for retries on processing failures without blocking the Vonage retry mechanism.
7. Secure Your Messaging Application
Webhook Signature Verification: We've implemented JWT verification, which is crucial. Always keep your VONAGE_API_SIGNATURE_SECRET secure.
Environment Variables: Never commit .env files or private.key to source control. Use environment variables provided by your deployment platform in production.
Input Validation/Sanitization: If you process the content of incoming messages (message.text) for anything beyond simple logging or replies (e.g., saving to a database, using it in commands), sanitize it thoroughly to prevent injection attacks (XSS, SQLi, etc.). Libraries like express-validator can help.
Rate Limiting: If your application exposes APIs that trigger messages, implement rate limiting (e.g., using express-rate-limit) to prevent abuse and control costs.
Authentication/Authorization: If you build a frontend or API layer on top of this, ensure proper user authentication and authorization before allowing actions like sending messages.
8. Troubleshoot Common Issues
Ngrok URL Mismatch: Ensure the exactngrok URL (including https://) is updated in both the Vonage Application settings and the WhatsApp Sandbox settings whenever you restart ngrok. A common mistake is forgetting one or using http instead of https.
Firewall Issues: If ngrok is running but you don't receive webhooks, a local firewall might be blocking ngrok or Node.js.
.env Not Loaded: Ensure require('dotenv').config(); is the very first line in server.js. Make sure the .env file is in the root directory where you run node server.js.
Incorrect Credentials: Double-check API Key, Secret, Application ID, Signature Secret, and Private Key Path in .env. Ensure the private.key file exists at the specified path.
Number Formatting: All phone numbers (to, from, MY_PHONE_NUMBER) should be in E.164 format without leading + or 00 (e.g., 14155551212).
WhatsApp Sandbox Limitations:
Only works with allowlisted numbers.
Uses the fixed Sandbox number (14157386102 or similar) as the from number.
Has time limits for conversations initiated by the business (requires user opt-in for production).
JWT Verification Errors: Usually caused by an incorrect VONAGE_API_SIGNATURE_SECRET in .env or issues with the system clock if the discrepancy is large.
Missing 200 OK: Forgetting to send res.status(200).end() in webhook handlers will cause Vonage to repeatedly retry sending the webhook, leading to duplicate processing and logs.
Vonage API Errors: Check the node server.js logs for specific errors returned by the Vonage API (e.g., insufficient funds, invalid recipient number, permission issues).
Frequently Asked Questions
How do I send WhatsApp messages with Vonage in Node.js?
Install the Vonage SDK (npm install @vonage/server-sdk @vonage/messages), authenticate with your API credentials and private key, then use vonage.messages.send(new WhatsAppText()) with your sandbox number as from and recipient number in E.164 format as to. Configure webhooks for delivery status and inbound messages through ngrok during development.
What is the Vonage WhatsApp Sandbox?
The Vonage WhatsApp Sandbox is a testing environment that lets you send and receive WhatsApp messages without a full WhatsApp Business Account. Users must allowlist their phone numbers by sending a specific message to the sandbox number (+14157386102). It's free but requires user opt-in and has conversation time limits.
How do I verify Vonage webhook signatures in Express.js?
Use the verifySignature function from @vonage/jwt to validate the Bearer token in the Authorization header against your Vonage API Signature Secret. This JWT verification ensures webhook requests genuinely originate from Vonage and haven't been tampered with, preventing unauthorized webhook abuse.
What is E.164 phone number format?
E.164 is the international phone numbering format: country code + national number without leading + or 00. Example: US number (415) 555-1212 becomes 14155551212. Vonage requires E.164 format for all phone numbers in API calls to ensure proper routing across global carriers.
How do I handle inbound WhatsApp messages with Vonage?
Create a POST endpoint (e.g., /webhooks/inbound) secured with JWT verification middleware. Parse req.body to extract message.text, message.from.id, and message.channel. Always respond with res.status(200).end() immediately to acknowledge receipt. Process messages asynchronously using background queues for complex logic.
Can I send SMS and WhatsApp from the same Vonage application?
Yes, the Vonage Messages API supports multi-channel messaging from a single application. Use different from numbers (Vonage SMS number for SMS, Sandbox number for WhatsApp) and specify channel: 'sms' or use WhatsAppText class. Configure webhooks once – they receive both SMS and WhatsApp inbound messages.
What Node.js version does Vonage SDK require?
Vonage SDK requires Node.js version 18 or higher for optimal compatibility with modern JavaScript features and security updates. Check your version with node --version. Update via nodejs.org or nvm. The SDK uses ES modules and async/await patterns requiring recent Node.js runtime features.
How do I deploy a Vonage webhook application to production?
Deploy to cloud platforms (AWS, Google Cloud, Heroku, Render) with HTTPS support. Use environment variables for credentials (never commit .env), implement pm2 process manager for reliability, configure production webhook URLs in Vonage Dashboard pointing to your public domain, and enable rate limiting with express-rate-limit to prevent abuse.
9. Deploy to Production
Deploying this application involves running the Node.js process on a server and ensuring it's publicly accessible for webhooks.
Choose a Platform: Options include cloud providers (AWS, Google Cloud, Azure), PaaS (Heroku, Render, Vercel - ensure long-running process support), or traditional VPS.
Environment Variables: Configure production environment variables securely on your chosen platform instead of using a .env file.
Process Manager: Use a process manager like pm2 to keep your Node.js application running reliably (handles crashes, restarts, logging).
HTTPS: Ensure your public server uses HTTPS for webhook URLs. Platforms often handle this, or use a reverse proxy like Nginx or Caddy with Let's Encrypt.
Update Webhook URLs: Update the Inbound and Status URLs in your Vonage Application and Sandbox settings to point to your permanent public server URL.
Firewall Rules: Ensure your server's firewall allows incoming traffic on the port your application listens on (or port 443 if using a reverse proxy).
Related Resources
Learn more about WhatsApp messaging and Node.js integrations:
Use the Vonage Messages API and Node.js SDK. The sendSms function handles sending text messages by specifying the recipient's number, message content, your Vonage SMS number, and the 'sms' channel. Ensure numbers are in E.164 format (e.g., 14155551212).
Set up an inbound webhook URL in your Vonage application settings and handle incoming messages in your Express app's /webhooks/inbound endpoint. Verify the webhook signature using @vonage/jwt to ensure security. Always respond with a 200 OK status, even if processing fails, to prevent Vonage retries.
Use the sendWhatsApp function, providing recipient and message text. The 'from' number must be your Sandbox number for testing or your provisioned WhatsApp Business number for production. This leverages the WhatsAppText class from @vonage/messages.
The Vonage Messages API is a unified platform for sending and receiving messages across various channels, including SMS, MMS, WhatsApp, Viber, and Facebook Messenger. It offers a simplified way to integrate messaging into applications using a single API.
Ngrok creates a secure tunnel that exposes your locally running Express.js application to the internet, allowing Vonage to deliver webhooks to your development environment. This is essential for testing webhooks before deploying to a public server.
Allowlist your WhatsApp number by sending a specific message to the Sandbox number provided in the Vonage Dashboard. Then, configure your Sandbox webhooks (inbound and status) to point to your application's URLs (e.g., your ngrok URL during development).
A Vonage Application ID is a unique identifier assigned to your application in the Vonage Dashboard. It's essential for associating your application with your Vonage account settings, such as API keys, webhooks, and linked phone numbers.
Replace ngrok with a publicly accessible server with a static IP or domain name before deploying your application to production. Ngrok is a development tool and not suitable for production use due to its temporary URLs and security implications.
Yes, using the Vonage WhatsApp Sandbox. It's designed for development and testing purposes. For production, a WhatsApp Business Account is required.
You need @vonage/server-sdk (core SDK), @vonage/messages (Messages API SDK), and @vonage/jwt (for verifying webhook signatures) to interact with the Vonage Messages API effectively in your Node.js project.
Use the verifyVonageSignature middleware to verify the JWT (JSON Web Token) signature attached to incoming webhooks. This confirms authenticity and prevents unauthorized requests from reaching your application logic.
Vonage sends message status updates via webhooks to inform your application about the delivery status of sent messages. These updates include statuses like 'submitted', 'delivered', 'rejected', or 'undeliverable', which are crucial for monitoring message delivery.
Verify webhook signatures by extracting the JWT (JSON Web Token) from the Authorization header of the webhook request. Use the @vonage/jwt library with your VONAGE_API_SIGNATURE_SECRET to decode and verify the token, ensuring its origin and integrity.
The .env file stores environment variables like API keys, secrets, and configuration settings. It's essential for managing sensitive credentials securely during development and should never be committed to version control.
The apiHost override was typically needed with older versions of the Vonage Node.js SDK, particularly when interacting with the Sandbox. Check the official documentation; recent SDK versions mostly handle the correct endpoint automatically.
This comprehensive guide teaches you how to build a production-ready WhatsApp and SMS integration using Node.js, Express.js, and the Vonage Messages API. Whether you're implementing customer notifications, two-factor authentication, or automated messaging workflows, this tutorial covers everything you need.
You'll learn how to send and receive WhatsApp messages with Node.js, implement secure webhook integration with Express.js, handle JWT authentication for API security, manage delivery status callbacks, and deploy your messaging application to production. Perfect for developers building customer communication platforms, chatbots, notification systems, or multi-channel messaging services.
Project Overview and Goals
What You'll Build:
You'll build an Express.js web application that:
Problem You'll Solve:
This application provides a foundational backend service for integrating programmable SMS and WhatsApp messaging into larger systems, enabling customer communication, notifications, two-factor authentication triggers, or basic chatbots.
Technologies Used:
Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its asynchronous nature, large ecosystem (npm), and popularity.
Express.js: A minimal and flexible Node.js web application framework. Provides robust features for web and mobile applications, ideal for creating webhook handlers and potentially a simple API.
Vonage Messages API: A unified API for sending and receiving messages across multiple channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber). Chosen for its multi-channel support and developer-friendly SDKs.
Vonage Node.js SDK (
@vonage/server-sdk,@vonage/messages,@vonage/jwt): Official libraries simplifying interaction with the Vonage APIs and handling security features like JWT verification.dotenv: A zero-dependency module that loads environment variables from a.envfile intoprocess.env. Essential for managing credentials securely.ngrok: A tool to expose local servers to the internet. Crucial for testing Vonage webhooks during development.Note: For production deployments,
ngrokis replaced by a publicly accessible server with a static IP or domain name.System Architecture:
Prerequisites:
ngrok: Installed and authenticated with a free account. (Download ngrok)1. Set Up Your Node.js WhatsApp Project
Let's create the project directory, initialize Node.js, and install the necessary dependencies.
Create Project Directory:
Open your terminal or command prompt and create a new directory for your project, then navigate into it.
mkdir vonage-messaging-app cd vonage-messaging-appInitialize Node.js Project:
Initialize a new Node.js project using npm. The
-yflag accepts the default settings.This creates a
package.jsonfile, which tracks your project's dependencies and other metadata.Install Dependencies:
Install Express, the Vonage SDK components, and
dotenv.express: Web framework.@vonage/server-sdk: Core Vonage SDK.@vonage/messages: Specific SDK for the Messages API.@vonage/jwt: For verifying webhook signatures.dotenv: For loading environment variables.Project Structure:
Your basic project structure should look like this:
Configure Vonage Account Settings:
Before writing code, ensure your Vonage account is set up correctly:
Set Default SMS API:
Why? Vonage has older SMS APIs. Setting Messages as default ensures consistency.
Create
.envFile for Credentials:Create a file named
.envin the root of your project directory. This file will store your sensitive credentials and configuration. Never commit this file to version control.YOUR_API_KEYwith your actual values (exceptVONAGE_APPLICATION_IDandVONAGE_PRIVATE_KEY, which we'll get next).+or00).Create
.gitignoreFile:To prevent accidentally committing sensitive information and unnecessary files, create a
.gitignorefile in the root directory:Initialize
server.js:Create a file named
server.jsin the root directory. This will contain our application logic. Add the initial setup code:// server.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const { Vonage } = require('@vonage/server-sdk'); const { WhatsAppText } = require('@vonage/messages'); const { verifySignature } = require('@vonage/jwt'); const app = express(); app.use(express.json()); // Middleware to parse JSON request bodies app.use(express.urlencoded({ extended: true })); // Middleware to parse URL-encoded request bodies const PORT = process.env.PORT || 8000; // --- Vonage Client Initialization (Add later) --- // --- Webhook Endpoints (Add later) --- // --- Send Functions (Add later) --- // --- Start Server --- app.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`); console.log(`Ensure ngrok is running and pointing to http://localhost:${PORT}`); }); // Basic check endpoint app.get('/', (req, res) => { res.status(200).send('Vonage Messaging App is running!'); });2. Configure Vonage WhatsApp Sandbox and Application
Now, let's create a Vonage Application and set up the WhatsApp Sandbox. This application acts as a container for your settings and authentication.
Create a Vonage Application:
Navigate to Applications in your Vonage API Dashboard.
Click + Create a new application.
Enter an Application name (e.g., "Node Express Messaging App").
Click Generate public and private key. This will automatically download a file named
private.key. Save this file in the root of your project directory. The public key is stored securely by Vonage.Why? This key pair is used for authenticating API requests securely instead of just using the API secret for certain operations.
Important: Copy the Application ID generated on this page and paste it into your
.envfile for theVONAGE_APPLICATION_IDvariable.Scroll down to Capabilities.
Toggle Messages ON.
ngrok. Leave them blank for now or enter temporary placeholders likehttp://example.com/inbound.Scroll down to Virtual numbers.
Click Link next to the Vonage phone number you purchased (the one you put in
.envasVONAGE_SMS_NUMBER). This connects incoming messages on that number to this application's webhooks.Click Generate new application.
Start
ngrok:Open a new terminal window/tab (keep the one for running the server separate). Run
ngrokto expose the port your Express app will listen on (defined in.env, default is 8000).ngrokwill display output similar to this:https://<YOUR_UNIQUE_ID>.ngrok-free.appForwarding URL. This is your public URL for testing.Configure Webhook URLs in Vonage Application:
Go back to your Vonage Application settings (Vonage API Dashboard -> Click your application name).
Click Edit.
Under Capabilities > Messages:
YOUR_NGROK_FORWARDING_URL/webhooks/inbound(e.g.,https://<YOUR_UNIQUE_ID>.ngrok-free.app/webhooks/inbound)YOUR_NGROK_FORWARDING_URL/webhooks/status(e.g.,https://<YOUR_UNIQUE_ID>.ngrok-free.app/webhooks/status)Why? Inbound URL receives incoming messages. Status URL receives delivery receipts/status updates for outbound messages.
Click Save changes.
Set Up Vonage WhatsApp Sandbox:
The Sandbox allows testing WhatsApp messaging without needing a full WhatsApp Business Account initially.
Navigate to Messages API Sandbox under Build & Manage in the Vonage API Dashboard.
Allowlist Your Number: Follow the instructions to send the specific WhatsApp message shown on the page (e.g., "Allowlist ") from your personal WhatsApp number to the Vonage Sandbox number provided (usually
+14157386102). This confirms you consent to receive test messages. The Sandbox number is what you should have in your.envfile asVONAGE_WHATSAPP_NUMBER.Configure Sandbox Webhooks: In the Sandbox settings page, find the Webhooks section.
YOUR_NGROK_FORWARDING_URL/webhooks/inboundYOUR_NGROK_FORWARDING_URL/webhooks/statusWhy? These tell the Sandbox where to send incoming WhatsApp messages and status updates, separate from the main application settings but typically pointing to the same place in simple apps.
Click Save webhooks.
Get Signature Secret:
Go to Settings in the Vonage API Dashboard.
Under API secrets, find the Signing secret associated with your main API key. Click the eye icon to reveal it.
Copy this secret and paste it into your
.envfile forVONAGE_API_SIGNATURE_SECRET.Why? This secret is used to create a signature (JWT) for incoming webhooks, allowing your application to verify they genuinely came from Vonage.
3. Send SMS and WhatsApp Messages with Vonage Messages API
Now, let's add the code to initialize the Vonage client and functions to send SMS and WhatsApp messages.
Initialize Vonage Client:
Add the following code to your
server.jsfile, below the initialrequireandappsetup:// server.js (Add this section) // --- Vonage Client Initialization --- 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 from .env }, { // Use sandbox host for WhatsApp testing if required by SDK version or specific needs // Check Vonage Node SDK documentation for latest recommendations // Often, the SDK handles the correct endpoint automatically based on message type // apiHost: 'https://messages-sandbox.nexmo.com' // Usually only needed for older SDKs or specific sandbox interactions } );.envfile to create an authenticated Vonage client instance.privateKeypath is read directly from the environment variable.apiHostoverride is generally not needed for sending messages with recent SDK versions but is included for awareness if you encounter issues specifically with the sandbox.Implement
sendSmsFunction:Add a function to handle sending SMS messages.
// server.js (Add this section) // --- Send Functions --- /** * Sends an SMS message using the Vonage Messages API. * @param {string} to - The recipient phone number (E.164 format, e.g., 12015551212). * @param {string} text - The message content. */ async function sendSms(to, text) { console.log(`Attempting to send SMS to ${to}...`); try { const resp = await vonage.messages.send({ message_type: 'text', text: text, to: to, from: process.env.VONAGE_SMS_NUMBER, // Your Vonage SMS number from .env channel: 'sms', }); console.log(`SMS sent successfully to ${to}. Message UUID: ${resp.messageUuid}`); return resp; // Contains message_uuid } catch (err) { console.error(`Error sending SMS to ${to}:`, err?.response?.data || err.message); // Rethrow or handle specific errors as needed throw err; } }vonage.messages.send.channel: 'sms'andmessage_type: 'text'.VONAGE_SMS_NUMBERfrom.envas the sender ID.Implement
sendWhatsAppFunction:Add a function to handle sending WhatsApp messages.
// server.js (Add this section) /** * Sends a WhatsApp message using the Vonage Messages API. * Uses the WhatsAppText class for simplicity. * @param {string} to - The recipient WhatsApp number (E.164 format, e.g., 12015551212). * @param {string} text - The message content. */ async function sendWhatsApp(to, text) { console.log(`Attempting to send WhatsApp message to ${to}...`); try { // NOTE: For Sandbox, 'from' MUST be the Sandbox number. // For production WhatsApp Business API, 'from' is your provisioned WhatsApp number. const fromNumber = process.env.VONAGE_WHATSAPP_NUMBER; const resp = await vonage.messages.send( new WhatsAppText({ text: text, to: to, from: fromNumber, }) ); console.log(`WhatsApp message sent successfully to ${to}. Message UUID: ${resp.messageUuid}`); return resp; // Contains message_uuid } catch (err) { console.error(`Error sending WhatsApp message to ${to}:`, err?.response?.data || err.message); // Log specific WhatsApp errors if available in err.response.data if (err.response?.data?.invalid_parameters) { console.error('Invalid Parameters:', err.response.data.invalid_parameters); } throw err; } }vonage.messages.sendwith aWhatsAppTextobject from@vonage/messages.VONAGE_WHATSAPP_NUMBER(which is the Sandbox number in our setup) as thefromnumber. This is required for the Sandbox.(Optional) Add Test Endpoints:
To easily trigger sending messages during development, you can add simple GET endpoints. Remember to remove or secure these properly before production.
// server.js (Add this section - for testing only) // --- Test Endpoints (Development Only - Secure or Remove for Production) --- app.get('/test/sms', async (req, res) => { const to = process.env.MY_PHONE_NUMBER; // Send to your own number from .env const text = `Hello from Vonage SMS! (${new Date().toLocaleTimeString()})`; if (!to) { return res.status(400).send('MY_PHONE_NUMBER environment variable not set.'); } try { const result = await sendSms(to, text); res.status(200).json({ message: 'SMS test initiated.', result }); } catch (error) { res.status(500).json({ message: 'Failed to send SMS test.', error: error.message }); } }); app.get('/test/whatsapp', async (req, res) => { const to = process.env.MY_PHONE_NUMBER; // Send to your own allowlisted number from .env const text = `Hello from Vonage WhatsApp Sandbox! (${new Date().toLocaleTimeString()})`; if (!to) { return res.status(400).send('MY_PHONE_NUMBER environment variable not set.'); } try { // Ensure your number is allowlisted in the Sandbox first! const result = await sendWhatsApp(to, text); res.status(200).json({ message: 'WhatsApp test initiated.', result }); } catch (error) { res.status(500).json({ message: 'Failed to send WhatsApp test.', error: error.message }); } });.envand call the respective send functions.4. Build Express.js Webhook Integration for Inbound Messages
Webhooks are HTTP callbacks triggered by Vonage when events occur (like receiving a message or a status update). Our Express app needs endpoints to listen for these callbacks.
Implement JWT Verification Middleware:
Create a middleware function to verify the signature of incoming webhooks. This ensures the request genuinely came from Vonage.
// server.js (Add this section) // --- Webhook Security Middleware --- const verifyVonageSignature = (req, res, next) => { try { // Get authorization header (should be "Bearer <JWT>") const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { console.warn('Webhook received without Bearer token.'); return res.status(401).send('Unauthorized: Missing Bearer token.'); } const token = authHeader.split(' ')[1]; // Verify the JWT using the signature secret from .env const decoded = verifySignature(token, process.env.VONAGE_API_SIGNATURE_SECRET); // Optional: Check claims like api_key or application_id if needed // if (decoded.api_key !== process.env.VONAGE_API_KEY) { // console.warn('Webhook received with mismatched API key.'); // return res.status(401).send('Unauthorized: Invalid API Key'); // } console.log('Webhook JWT verified successfully.'); req.vonage_jwt = decoded; // Attach decoded payload if needed later next(); // Proceed to the next middleware/handler } catch (error) { console.error('Webhook JWT verification failed:', error.message); res.status(401).send('Unauthorized: Invalid signature.'); } };Authorization: Bearer <token>header.verifySignaturefrom@vonage/jwtand yourVONAGE_API_SIGNATURE_SECRET.401 Unauthorizedresponse if verification fails.next()to pass control to the actual route handler if verification succeeds.Create Inbound Webhook Endpoint:
This endpoint (
/webhooks/inbound) will receive incoming SMS and WhatsApp messages.// server.js (Add this section) // --- Webhook Endpoints --- // Endpoint to receive incoming messages (SMS & WhatsApp) // Vonage sends POST requests to this URL app.post('/webhooks/inbound', verifyVonageSignature, async (req, res) => { console.log('--- Inbound Webhook Received ---'); console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the full payload const message = req.body; // Basic sanity check for expected properties if (!message.from || !message.to || !message.channel) { console.warn('Received incomplete inbound message data.'); // Still send 200 OK to prevent Vonage retries for malformed requests return res.status(200).end(); } console.log(`Received message on channel: ${message.channel}`); console.log(`From: ${message.from.number || message.from.id}`); // Number format varies by channel console.log(`To: ${message.to.number || message.to.id}`); if (message.message_type === 'text') { console.log(`Text: ${message.text}`); // --- Example: Auto-reply logic --- if (message.channel === 'whatsapp') { try { // Simple reply to incoming WhatsApp const replyText = `Thanks for your WhatsApp message: "${message.text}"`; await sendWhatsApp(message.from.id, replyText); // Reply to the sender console.log(`Sent WhatsApp auto-reply to ${message.from.id}`); } catch (error) { console.error(`Failed to send WhatsApp auto-reply to ${message.from.id}`, error); } } else if (message.channel === 'sms') { // Optional: Implement SMS auto-reply logic here // const replyText = `Thanks for your SMS: "${message.text}"`; // await sendSms(message.from.number, replyText); } // --- End Example --- } else { console.log(`Received non-text message type: ${message.message_type}`); // Handle other message types (image, audio, location, etc.) if needed } // IMPORTANT: Always respond with 200 OK quickly to acknowledge receipt. // Vonage will retry if it doesn't get a 200 response. res.status(200).end(); });verifyVonageSignaturemiddleware first.req.body). The structure varies slightly depending on the channel (SMS vs. WhatsApp) and message type. Refer to Vonage Messages API Webhook Reference for details.200 OKresponse to Vonage to acknowledge receipt, even if processing fails later. This prevents unnecessary retries from Vonage.Create Status Webhook Endpoint:
This endpoint (
/webhooks/status) receives updates about the delivery status of messages you sent.// server.js (Add this section) // Endpoint to receive message status updates // Vonage sends POST requests to this URL app.post('/webhooks/status', verifyVonageSignature, (req, res) => { console.log('--- Status Webhook Received ---'); console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the full payload const statusUpdate = req.body; // Example: Log key status information if (statusUpdate.message_uuid && statusUpdate.status) { console.log(`Status Update for ${statusUpdate.message_uuid}: ${statusUpdate.status}`); if(statusUpdate.error) { console.error(`Error (${statusUpdate.error.code}): ${statusUpdate.error.reason}`); } // You could update a database record here based on message_uuid and status // Statuses include: submitted, delivered, rejected, undeliverable } else { console.warn('Received incomplete status update data.'); } // IMPORTANT: Always respond with 200 OK quickly. res.status(200).end(); });verifyVonageSignaturemiddleware.message_uuid,status(submitted,delivered,rejected,undeliverable),timestamp, and potentially anerrorobject. See Vonage Messages API Webhook Reference.200 OKresponse.5. Test Your WhatsApp and SMS Integration
Now you can run the application and test the integrations.
Ensure
ngrokis Running:Make sure the
ngrok http 8000command is still running in its separate terminal and that the correctngrokForwarding URL is configured in your Vonage Application and Sandbox settings.Start the Node.js Server:
In the terminal window within your project directory, run:
You should see:
Test Sending SMS:
http://localhost:8000/test/sms.node server.js. You should see logs indicating an attempt to send SMS and then success with amessage_uuid.MY_PHONE_NUMBERspecified in.env). You should receive the SMS message shortly.submitted,delivered).Test Sending WhatsApp:
MY_PHONE_NUMBERis allowlisted in the Vonage WhatsApp Sandbox.http://localhost:8000/test/whatsapp.node server.jsterminal for logs indicating the attempt and success.Test Receiving SMS:
VONAGE_SMS_NUMBER.node server.jsterminal. You should see the "Inbound Webhook Received" logs, including the message details (channel: sms, your phone number, the text).Test Receiving WhatsApp (and Auto-Reply):
VONAGE_WHATSAPP_NUMBER(the Sandbox number).node server.jsterminal. You should see:channel: whatsapp, your number, the text).6. Handle Errors and Add Logging
The current implementation has basic
console.logandconsole.error. For production:winstonorpino. This enables structured logging (e.g., JSON format), different log levels (debug, info, warn, error), and routing logs to files or external services.err.response.dataobject in thecatchblocks of thesendfunctions often contains detailed error information from the Vonage API, including specific error codes and descriptions. Log these details and potentially handle specific, retryable errors (like network issues) differently from non-retryable ones (like invalid numbers). Refer to Vonage API Error Codes.200 OKimmediately, implement background job queues (e.g., using BullMQ with Redis) for processing inbound messages or sending replies if the logic is complex or involves external dependencies. This prevents webhook timeouts and allows for retries on processing failures without blocking the Vonage retry mechanism.7. Secure Your Messaging Application
VONAGE_API_SIGNATURE_SECRETsecure..envfiles orprivate.keyto source control. Use environment variables provided by your deployment platform in production.message.text) for anything beyond simple logging or replies (e.g., saving to a database, using it in commands), sanitize it thoroughly to prevent injection attacks (XSS, SQLi, etc.). Libraries likeexpress-validatorcan help.express-rate-limit) to prevent abuse and control costs.8. Troubleshoot Common Issues
ngrokURL (includinghttps://) is updated in both the Vonage Application settings and the WhatsApp Sandbox settings whenever you restartngrok. A common mistake is forgetting one or usinghttpinstead ofhttps.ngrokis running but you don't receive webhooks, a local firewall might be blockingngrokor Node.js..envNot Loaded: Ensurerequire('dotenv').config();is the very first line inserver.js. Make sure the.envfile is in the root directory where you runnode server.js..env. Ensure theprivate.keyfile exists at the specified path.to,from,MY_PHONE_NUMBER) should be in E.164 format without leading+or00(e.g.,14155551212).14157386102or similar) as thefromnumber.VONAGE_API_SIGNATURE_SECRETin.envor issues with the system clock if the discrepancy is large.200 OK: Forgetting to sendres.status(200).end()in webhook handlers will cause Vonage to repeatedly retry sending the webhook, leading to duplicate processing and logs.node server.jslogs for specific errors returned by the Vonage API (e.g., insufficient funds, invalid recipient number, permission issues).Frequently Asked Questions
How do I send WhatsApp messages with Vonage in Node.js?
Install the Vonage SDK (
npm install @vonage/server-sdk @vonage/messages), authenticate with your API credentials and private key, then usevonage.messages.send(new WhatsAppText())with your sandbox number asfromand recipient number in E.164 format asto. Configure webhooks for delivery status and inbound messages through ngrok during development.What is the Vonage WhatsApp Sandbox?
The Vonage WhatsApp Sandbox is a testing environment that lets you send and receive WhatsApp messages without a full WhatsApp Business Account. Users must allowlist their phone numbers by sending a specific message to the sandbox number (+14157386102). It's free but requires user opt-in and has conversation time limits.
How do I verify Vonage webhook signatures in Express.js?
Use the
verifySignaturefunction from@vonage/jwtto validate the Bearer token in theAuthorizationheader against your Vonage API Signature Secret. This JWT verification ensures webhook requests genuinely originate from Vonage and haven't been tampered with, preventing unauthorized webhook abuse.What is E.164 phone number format?
E.164 is the international phone numbering format: country code + national number without leading + or 00. Example: US number (415) 555-1212 becomes 14155551212. Vonage requires E.164 format for all phone numbers in API calls to ensure proper routing across global carriers.
How do I handle inbound WhatsApp messages with Vonage?
Create a POST endpoint (e.g.,
/webhooks/inbound) secured with JWT verification middleware. Parsereq.bodyto extractmessage.text,message.from.id, andmessage.channel. Always respond withres.status(200).end()immediately to acknowledge receipt. Process messages asynchronously using background queues for complex logic.Can I send SMS and WhatsApp from the same Vonage application?
Yes, the Vonage Messages API supports multi-channel messaging from a single application. Use different
fromnumbers (Vonage SMS number for SMS, Sandbox number for WhatsApp) and specifychannel: 'sms'or useWhatsAppTextclass. Configure webhooks once – they receive both SMS and WhatsApp inbound messages.What Node.js version does Vonage SDK require?
Vonage SDK requires Node.js version 18 or higher for optimal compatibility with modern JavaScript features and security updates. Check your version with
node --version. Update via nodejs.org or nvm. The SDK uses ES modules and async/await patterns requiring recent Node.js runtime features.How do I deploy a Vonage webhook application to production?
Deploy to cloud platforms (AWS, Google Cloud, Heroku, Render) with HTTPS support. Use environment variables for credentials (never commit
.env), implementpm2process manager for reliability, configure production webhook URLs in Vonage Dashboard pointing to your public domain, and enable rate limiting withexpress-rate-limitto prevent abuse.9. Deploy to Production
Deploying this application involves running the Node.js process on a server and ensuring it's publicly accessible for webhooks.
.envfile.pm2to keep your Node.js application running reliably (handles crashes, restarts, logging).Related Resources
Learn more about WhatsApp messaging and Node.js integrations: