code examples
code examples
Vonage WhatsApp Integration with Node.js: Complete Express Tutorial 2025
Learn to build a production-ready Node.js Express app with Vonage Messages API for SMS and WhatsApp. Complete guide includes webhook security, JWT authentication, sandbox setup, and deployment best practices.
Vonage Node.js WhatsApp Integration: Complete Express Tutorial
Estimated completion time: 60-90 minutes (including account setup and testing)
Build a Node.js application using Express to send and receive both SMS and WhatsApp messages via the Vonage Messages API. This guide covers environment setup, Vonage configuration, core message logic, secure webhook handling, and troubleshooting.
Real-world use cases:
- Two-factor authentication (2FA) codes sent via SMS or WhatsApp
- Order confirmation and shipping notifications for e-commerce
- Appointment reminders for healthcare and service providers
- Customer support chatbots responding across multiple channels
- Marketing campaigns with opt-in messaging compliance
By the end of this tutorial, you'll have a functional Express server capable of:
- Sending outbound SMS messages.
- Sending outbound WhatsApp messages (using the Vonage Sandbox).
- Receiving inbound SMS messages via webhooks.
- Receiving inbound WhatsApp messages via webhooks.
- Securely verifying incoming webhook requests from Vonage.
Create applications that communicate with users on their preferred messaging channels through a unified API.
Project Overview and Goals
Goal: Create a simple yet robust Node.js Express application demonstrating two-way SMS and WhatsApp communication with the Vonage Messages API.
Problem Solved: Managing communication across different channels (like SMS and WhatsApp) typically requires separate integrations. The Vonage Messages API provides a single interface, simplifying development and letting you reach users more effectively. For example, a healthcare provider can send appointment reminders via SMS to patients without smartphones while using WhatsApp for patients who prefer app-based messaging—all through a single codebase. This guide solves the initial setup and integration challenge for developers starting with Vonage messaging in Node.js.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express.js: A minimal and flexible Node.js web application framework used to create the server and API endpoints/webhooks.
- Vonage Messages API: A unified API for sending and receiving messages across multiple channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber).
- Vonage Node.js SDK: Simplifies interaction with Vonage APIs within a Node.js application.
- ngrok: A tool to expose local development servers to the internet, necessary for receiving Vonage webhooks during development. Production deployments require a stable, publicly accessible URL.
- dotenv: A module to load environment variables from a
.envfile intoprocess.env.
System Architecture:
+-----------------+ (Send SMS/WhatsApp) +---------------------+ +-----------------+
| Your Application| ----------------------------> | Vonage Messages API | ---> | User's Phone |
| (Node.js/Express)| (Receive SMS/WhatsApp) | (SMS/WhatsApp) | | (SMS/WhatsApp) |
+-----------------+ <---------------------------- +---------------------+ <--- +-----------------+
^ | ^
| | (Webhook Post) | (Webhook Post)
| +----------------------+ (Inbound/Status)
|
+------v----------+
| ngrok Tunnel |
+-----------------+
| (Forwarding)
+------v----------+
| Localhost:PORT |
+-----------------+
Data flow and latency:
- Outbound messages typically deliver within 1-5 seconds for SMS, 2-10 seconds for WhatsApp (depending on network conditions and message queue)
- Webhook notifications arrive within 0.5-3 seconds after events (delivery confirmations, inbound messages)
- JWT signatures in webhooks expire 5 minutes after issuance; ensure server time is NTP-synchronized
- Message throughput: ~20 messages/second by default (Vonage hosting), up to 80 messages/second with auto-scaling (WhatsApp Cloud hosting)
Prerequisites:
- Node.js: Installed (Version 22 or higher recommended). Node.js 22 is the current LTS version for 2025, providing Active LTS support until October 2025 and Maintenance LTS until April 2027, making it the best choice for production applications. Check with
node -v. Download Node.js - npm: Node Package Manager, included with Node.js. Check with
npm -v. - Vonage API Account: Sign up for Vonage if you don't have one. Free credit is available for new accounts.
- ngrok: Installed, with a free account set up. Download ngrok
- A Mobile Phone: Capable of sending/receiving SMS and WhatsApp messages for testing.
- Basic knowledge required: Familiarity with JavaScript ES6+ syntax (async/await, promises), REST API concepts, command-line terminal operations, and basic understanding of HTTP methods (GET, POST) and status codes.
1. Setting Up Your Node.js Project for Vonage Integration
Initialize your Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
bashmkdir vonage-messaging-app cd vonage-messaging-app -
Initialize Node.js Project: Initialize the project using npm. The
-yflag accepts the default settings.bashnpm init -yThis creates a
package.jsonfile. -
Install Dependencies: Install Express, the Vonage SDKs (
server-sdkfor core functionality,messagesfor message types,jwtfor signature verification), anddotenv. As of late 2025,@vonage/server-sdkis at version 3.24.1+ and@vonage/messagesis at version 1.20.3+.bashnpm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenvWhat each package does:
express: Web framework for creating API endpoints and handling HTTP requests@vonage/server-sdk: Core SDK providing authentication and API client initialization@vonage/messages: Message type helpers (WhatsAppText, SMS) for constructing properly formatted message payloads@vonage/jwt: JWT verification utilities for secure webhook authenticationdotenv: Securely loads environment variables from.envfiles intoprocess.env
-
Create Core Files: Create the main application file and a file for environment variables.
bashtouch index.js .env .gitignore -
Configure
.gitignore: Never commit sensitive information or unnecessary files. Add the following lines to your.gitignorefile:text# Environment variables .env # Node dependencies node_modules/ # Vonage private key private.key # OS generated files .DS_Store Thumbs.db -
Set Up Environment Variables (
.env): Open the.envfile and add the following variables. We will fill in the values in the next steps.dotenv# Vonage API Credentials VONAGE_API_KEY= VONAGE_API_SECRET= VONAGE_APPLICATION_ID= VONAGE_PRIVATE_KEY=./private.key # Path to your downloaded private key file VONAGE_API_SIGNATURE_SECRET= # Vonage Numbers VONAGE_NUMBER= # Your purchased Vonage SMS-capable number VONAGE_WHATSAPP_NUMBER= # Your Vonage WhatsApp Sandbox number (e.g., 14157386102) # Server Configuration PORT=3000 # Port for the Express server- Purpose of Each Variable:
VONAGE_API_KEY,VONAGE_API_SECRET: Your primary Vonage account credentials. Found on the Vonage API Dashboard homepage.VONAGE_APPLICATION_ID: The unique ID for the Vonage Application you'll create to handle messaging.VONAGE_PRIVATE_KEY: The file path to the private key downloaded when creating the Vonage Application. Crucial for authenticating SDK requests. Important: Like the.envfile, ensure theprivate.keyfile itself is listed in.gitignoreand never committed to version control.VONAGE_API_SIGNATURE_SECRET: Used to verify the authenticity of incoming webhooks. Found in your Vonage Dashboard settings.VONAGE_NUMBER: The virtual phone number you purchase/rent from Vonage, capable of sending/receiving SMS.VONAGE_WHATSAPP_NUMBER: The specific number provided by Vonage for sending WhatsApp messages, especially the Sandbox number during development.PORT: The local port your Express server will listen on.
- Purpose of Each Variable:
Project Structure:
Your project directory should now look like this:
vonage-messaging-app/
├── .env
├── .gitignore
├── index.js
├── package.json
├── package-lock.json
└── node_modules/
└── ... (installed dependencies)
(You will add private.key later)
2. Configuring Vonage Account Settings
Configure the necessary components within your Vonage account.
-
Retrieve API Key and Secret:
- Log in to your Vonage API Dashboard.
- On the main page, find your API key and API secret.
- Copy these values and paste them into your
.envfile forVONAGE_API_KEYandVONAGE_API_SECRET.
-
Retrieve Signature Secret:
- In the Vonage Dashboard, navigate to your Settings page (usually accessible from the profile/account menu).
- Find the API settings section. Locate your Signature secret.
- If signature secret doesn't exist: Generate one by clicking "Generate signature secret" in the API settings. The secret must be at least 32 bits (4 characters) for security. Once generated, copy it immediately as it won't be shown again.
- Copy this value and paste it into your
.envfile forVONAGE_API_SIGNATURE_SECRET.
-
Purchase a Vonage Number (for SMS):
- In the dashboard, go to Numbers > Buy numbers.
- Search for a number with SMS capability in your desired country. Consider Voice capability as well if needed later.
- Pricing: Number costs vary by country—typically $0.90-$5.00/month for US numbers, with one-time setup fees ranging from $0.00-$2.00. Check the Vonage pricing page for your region.
- Purchase a number.
- Copy the purchased number (including the country code, e.g.,
12015550123) and paste it into your.envfile forVONAGE_NUMBER. Important: The SDK typically expects the number without a leading+or00(e.g.,12015550123), but always consult the latest Vonage Node SDK documentation for the exact required format.
-
Create a Vonage Application: Vonage Applications act as containers for your communication configurations (like webhooks) and link specific numbers to your code.
- In the dashboard, go to Applications > Create a new application.
- Give your application a descriptive name (e.g., "Node Express Messenger").
- Click Generate public and private key. This automatically downloads a
private.keyfile. Save this file in the root of your project directory (vonage-messaging-app/). Your.envfile already points to./private.key. (Why? The SDK uses this private key to sign requests, authenticating them with the corresponding public key stored by Vonage.) - Enable the Messages capability by toggling it on.
- You'll see fields for Inbound URL and Status URL. Fill these in after setting up ngrok. Leave them blank for now.
- Click Generate new application.
- On the application details page, copy the Application ID.
- Paste this ID into your
.envfile forVONAGE_APPLICATION_ID.
-
Link Your Vonage Number to the Application:
- On the same application details page, scroll down to the Link numbers section.
- Find the Vonage number you purchased earlier and click the Link button next to it. (Why? This tells Vonage that any messages sent to this number should trigger the webhooks configured in this specific application.)
-
Set Up ngrok: ngrok creates a secure tunnel from the public internet to your local machine, allowing Vonage's servers to reach your development server.
-
Open a new terminal window (keep the first one for running the Node app later).
-
Navigate to your project directory (optional, but good practice).
-
Run ngrok, telling it to forward to the port defined in your
.envfile (default is 3000).bashngrok http 3000 -
ngrok will display output including a
ForwardingURL ending in.ngrok.ioor.ngrok.app(e.g.,https://<unique-id>.ngrok.io). Copy this HTTPS URL.
Common ngrok troubleshooting:
- "command not found": Ensure ngrok is installed and added to your system PATH
- Port already in use: Change the port in
.envor stop the conflicting process - Session expired (free tier): ngrok free sessions last 2 hours; restart ngrok and update webhook URLs
- Firewall blocking: Check corporate/network firewall settings; ngrok requires outbound HTTPS (port 443)
-
-
Configure Webhook URLs in Vonage Application:
- Go back to your Vonage Application settings in the dashboard (Applications > Click your application name).
- Under the Messages capability:
- Paste the ngrok HTTPS URL into the Inbound URL field and append
/webhooks/inbound. Example:https://<unique-id>.ngrok.io/webhooks/inbound - Paste the ngrok HTTPS URL into the Status URL field and append
/webhooks/status. Example:https://<unique-id>.ngrok.io/webhooks/status
- Paste the ngrok HTTPS URL into the Inbound URL field and append
- Scroll down and click Save changes. (Why? When Vonage receives an inbound message for your linked number, it sends the message data via HTTP POST to the Inbound URL. When the status of an outbound message changes (e.g., delivered, failed), it sends an update to the Status URL.)
-
Set Up Messages API Sandbox (for WhatsApp): The Sandbox provides a testing environment for WhatsApp without requiring a full WhatsApp Business Account setup initially. The sandbox allows you to send free test messages to up to 5 allowlisted recipient phone numbers.
- In the Vonage dashboard, navigate to Developer Tools > Messages API Sandbox.
- You'll see instructions to connect your personal WhatsApp number to the Sandbox (usually involves sending a specific message or scanning a QR code from your WhatsApp). Follow these instructions. Your number is now "allowlisted" for testing.
- Note the Vonage Sandbox WhatsApp Number provided on this page (it's often
14157386102). Copy this number and paste it into your.envfile forVONAGE_WHATSAPP_NUMBER. - Click the Webhooks tab within the Sandbox configuration.
- Paste your ngrok HTTPS URL into the Inbound Message URL field, appending
/webhooks/inbound(same as the application inbound URL). - Paste your ngrok HTTPS URL into the Message Status URL field, appending
/webhooks/status(same as the application status URL). - Click Save webhooks.
Production WhatsApp Requirements: For production use beyond the sandbox, you need: (1) A verified Meta Business Manager account, (2) A dedicated phone number (not connected to another WhatsApp account), (3) WhatsApp Business Account (WABA) approval from Meta, (4) Compliance with WhatsApp Commerce policies, and (5) Industry eligibility verification (WhatsApp restricts API access for gambling, alcohol, adult content, and other prohibited industries). Businesses with unverified Meta Business Manager accounts are limited to 2 phone numbers, while verified accounts can have up to 20 phone numbers across all WABAs.
Meta verification process: After submitting your business verification documents (business license, utility bills, domain-linked email), Meta typically reviews within 2-7 business days if documents are correct. The verification timeline can extend to 14 business days during high-volume periods. Display name approval (for the green verified badge) follows business verification and typically completes within 24-48 hours. For detailed compliance requirements, see Meta's WhatsApp Business Policy.
-
Ensure Messages API is Default for SMS:
- Go to API Settings in the dashboard.
- Under SMS settings, ensure Default SMS Setting is set to
Use the Messages API. Save changes if necessary. (Why? Vonage has older SMS APIs. This ensures your application uses the modern Messages API consistently.)
Configuration is complete! You have your credentials, numbers, application, webhooks, and sandbox set up.
3. Implementing the Express Server and Core Logic
Now let's write the Node.js code in index.js.
// index.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');
// --- Initialization ---
const app = express();
app.use(express.json()); // Middleware to parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Middleware to parse URL-encoded bodies
const port = process.env.PORT || 3000; // Use port from .env or default to 3000
// Initialize Vonage client
// Ensure all required environment variables are present
if (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET || !process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_PRIVATE_KEY || !process.env.VONAGE_API_SIGNATURE_SECRET) {
console.error("FATAL ERROR: Required Vonage environment variables are missing. Check your .env file.");
process.exit(1); // Exit if essential config is missing
}
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
});
// --- Security Middleware: Verify Vonage Signature ---
// This function verifies that incoming webhook requests genuinely originated from Vonage.
// Vonage uses JWT Bearer Authorization with HMAC-SHA256 signatures for webhook security.
const verifyVonageSignature = (req, res, next) => {
try {
// Extract Bearer token (JWT) from Authorization header
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.startsWith('Bearer ') ? authHeader.split(' ')[1] : null;
if (!token) {
console.warn('Signature missing or invalid format in Authorization header.');
return res.status(401).send('Unauthorized: Signature missing or improperly formatted');
}
// The body needs to be passed exactly as received for verification.
// express.json() middleware is generally compatible if it runs before this middleware.
// IMPORTANT: The signature secret should be at least 32 bits for security.
// The JWT signature expires 5 minutes after issuance, so ensure your server time is synchronized (use NTP).
const isSignatureValid = verifySignature(token, process.env.VONAGE_API_SIGNATURE_SECRET, req.body);
if (isSignatureValid) {
console.log('Webhook signature verified successfully.');
next(); // Proceed to the route handler
} else {
console.warn('Invalid signature.');
res.status(401).send('Unauthorized: Invalid signature');
}
} catch (error) {
console.error('Error verifying signature:', error);
res.status(500).send('Internal Server Error during signature verification');
}
};
// --- API Endpoint for Sending Messages ---
// POST /send-message
// Body: { "type": "sms" | "whatsapp", "to": "recipient_number", "text": "message_content" }
app.post('/send-message', async (req, res) => {
const { type, to, text } = req.body;
// Basic input validation
if (!type || !to || !text || (type !== 'sms' && type !== 'whatsapp')) {
return res.status(400).json({ error: 'Missing or invalid parameters. Required: type ("sms" or "whatsapp"), to, text' });
}
if (!process.env.VONAGE_NUMBER && type === 'sms') {
return res.status(500).json({ error: 'VONAGE_NUMBER not configured in .env for sending SMS.' });
}
if (!process.env.VONAGE_WHATSAPP_NUMBER && type === 'whatsapp') {
return res.status(500).json({ error: 'VONAGE_WHATSAPP_NUMBER not configured in .env for sending WhatsApp.' });
}
const fromNumber = type === 'sms' ? process.env.VONAGE_NUMBER : process.env.VONAGE_WHATSAPP_NUMBER;
console.log(`Attempting to send ${type} message from ${fromNumber} to ${to}`);
try {
let response;
if (type === 'sms') {
response = await vonage.messages.send({
message_type: "text",
text: text,
to: to,
from: fromNumber,
channel: "sms"
});
} else { // whatsapp
response = await vonage.messages.send(
new WhatsAppText({
text: text,
to: to,
from: fromNumber // Use the Sandbox number from .env
})
);
}
console.log(`Message submitted successfully with UUID: ${response.message_uuid}`);
res.status(202).json({ // 202 Accepted: Request is accepted, processing hasn't completed
message: `Message (${type}) submitted successfully.`,
message_uuid: response.message_uuid
});
} catch (error) {
console.error("Error sending message:", error?.response?.data || error.message || error);
// Provide more specific feedback if possible
let statusCode = 500;
let errorMessage = 'Failed to send message due to an internal error.';
if (error.response && error.response.data) {
statusCode = error.response.status || 500;
errorMessage = error.response.data.title || error.response.data.detail || errorMessage;
} else if (error.message) {
errorMessage = error.message;
}
// TODO: Consider adding more specific error handling based on Vonage error codes (e.g., authentication, invalid number format)
res.status(statusCode).json({ error: errorMessage, details: error?.response?.data });
}
});
// --- Webhook Endpoints ---
// POST /webhooks/inbound
// Handles incoming messages from users (SMS or WhatsApp)
// Apply signature verification middleware ONLY to webhook routes
app.post('/webhooks/inbound', verifyVonageSignature, (req, res) => {
console.log('--- Inbound Message Received ---');
console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the entire payload
// Example: Log basic info
const { from, to, channel, message_type, text, timestamp } = req.body;
if (from && channel && message_type) {
console.log(`Message received from ${from.number || from.id} on channel ${channel} (${message_type}) at ${timestamp}`);
if (text) { // Check if text exists before logging
console.log(`Content: ${text}`);
}
// Add your logic here: save to database, trigger replies, etc.
} else {
console.warn("Received inbound payload with unexpected structure.");
}
// IMPORTANT: Always respond with 200 OK to Vonage webhooks
// Failure to do so will cause Vonage to retry sending the webhook,
// potentially leading to duplicate processing.
res.status(200).end();
});
// POST /webhooks/status
// Handles status updates for outbound messages (e.g., delivered, failed)
app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
console.log('--- Message Status Update Received ---');
console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the entire payload
// Example: Log status info
const { message_uuid, status, timestamp, to, from, error } = req.body;
if (message_uuid && status && timestamp) {
console.log(`Status for message ${message_uuid} to ${to.number || to.id}: ${status} at ${timestamp}`);
if (error) {
console.error(` Error details: Code ${error.code}, Reason: ${error.reason}`);
}
// Add your logic here: update message status in your database, trigger alerts on failure, etc.
} else {
console.warn("Received status payload with unexpected structure.");
}
// IMPORTANT: Always respond with 200 OK
res.status(200).end();
});
// --- Start Server ---
app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`);
console.log('Ensure ngrok is running and forwarding to this port.');
console.log('Vonage webhook URLs should point to your ngrok HTTPS address.');
});Example webhook payloads for reference:
Inbound SMS webhook:
{
"channel": "sms",
"message_uuid": "aaaaaaaa-bbbb-4ccc-8ddd-0123456789ab",
"to": "447700900000",
"from": "447700900001",
"timestamp": "2025-02-03T12:14:25Z",
"text": "Hello From Customer!",
"sms": {
"num_messages": "1",
"keyword": "HELLO"
}
}Status webhook (delivered):
{
"message_uuid": "aaaaaaaa-bbbb-4ccc-8ddd-0123456789ab",
"to": "447700900000",
"from": "447700900001",
"timestamp": "2025-02-03T12:14:28Z",
"status": "delivered",
"channel": "sms",
"usage": {
"currency": "EUR",
"price": "0.0333"
}
}Code Explanation:
- Imports & Setup: Loads
.envvariables, imports necessary modules (Express, Vonage SDK parts), initializes Express, and sets up middleware for parsing request bodies. - Vonage Client Initialization: Creates a
Vonageclient instance using credentials and the private key path from environment variables. Includes a check for missing essential variables. - Signature Verification Middleware (
verifyVonageSignature):- This function is designed to be used as middleware specifically for the webhook routes (
/webhooks/inbound,/webhooks/status). - It extracts the JWT token from the
Authorization: Bearer <token>header. - It uses
verifySignaturefrom@vonage/jwt, passing the token, yourVONAGE_API_SIGNATURE_SECRET(from.env), and the raw request body. Note: Usingexpress.json()middleware parses the body before this point, which is compatible withverifySignatureas long asexpress.json()is configured correctly and no other middleware modifies the body before verification. It's crucial that the body passed matches exactly what Vonage sent. - If the signature is valid, it calls
next()to pass control to the actual route handler. - If invalid or missing, it sends a
401 Unauthorizedresponse and stops processing. This is crucial for security.
- This function is designed to be used as middleware specifically for the webhook routes (
- Send Message Endpoint (
/send-message):- Defines a
POSTroute. - Expects
type('sms' or 'whatsapp'),to(recipient number), andtextin the JSON request body. - Performs basic validation on input parameters and checks if the required Vonage numbers are configured.
- Selects the correct
fromnumber based on thetype. - Uses
vonage.messages.send():- For SMS: Passes an object with
message_type: "text",channel: "sms", etc. - For WhatsApp: Uses the
WhatsAppTexthelper class for structuring the payload correctly.
- For SMS: Passes an object with
- Logs success or error. Uses
async/awaitfor cleaner handling of the promise returned by the SDK. - Returns a
202 Acceptedstatus on successful submission, along with themessage_uuid. - Includes basic error handling, attempting to extract meaningful error messages from the Vonage SDK response.
- Defines a
- Inbound Webhook Endpoint (
/webhooks/inbound):- Defines a
POSTroute that uses theverifyVonageSignaturemiddleware first. - Logs the received message payload. You would add database saving or other processing logic here.
- Crucially, sends a
200 OKstatus back to Vonage immediately.
- Defines a
- Status Webhook Endpoint (
/webhooks/status):- Defines a
POSTroute, also protected byverifyVonageSignature. - Logs the received status update payload (e.g.,
delivered,failed,read). You would add logic to update your application's state based on this. - Also sends
200 OKback to Vonage.
- Defines a
- Server Start: Starts the Express server, listening on the configured port.
4. Testing Your Vonage WhatsApp and SMS Integration
-
Ensure ngrok is Running: Verify that your
ngrok http 3000command is still active in its terminal window and that the HTTPS URL matches the one configured in Vonage. -
Start the Node.js Server: In your primary terminal window (in the
vonage-messaging-appdirectory), run:bashnode index.jsYou should see the "Server listening..." message.
-
Test Sending SMS: Use a tool like
curlor Postman to send a POST request to your/send-messageendpoint. Replace<YOUR_PHONE_NUMBER>with your actual mobile number (including country code, no '+').bashcurl -X POST http://localhost:3000/send-message \ -H "Content-Type: application/json" \ -d '{ "type": "sms", "to": "<YOUR_PHONE_NUMBER>", "text": "Hello from Vonage SMS via Node.js!" }'- Check: You should receive the SMS on your phone. Check the Node.js console logs for success messages and the status webhook logs for delivery updates.
-
Test Sending WhatsApp: Ensure your WhatsApp number is allowlisted in the Vonage Sandbox. Replace
<YOUR_WHATSAPP_NUMBER>with your allowlisted number.bashcurl -X POST http://localhost:3000/send-message \ -H "Content-Type: application/json" \ -d '{ "type": "whatsapp", "to": "<YOUR_WHATSAPP_NUMBER>", "text": "Hello from Vonage WhatsApp via Node.js Sandbox!" }'- Check: You should receive the WhatsApp message on your phone. Check console logs.
-
Test Receiving SMS:
- From your mobile phone, send an SMS message to your purchased Vonage number (
VONAGE_NUMBER). - Check: Watch the Node.js console. You should see the "Inbound Message Received" log, including the message content and sender number. Verify the signature verification passed.
- From your mobile phone, send an SMS message to your purchased Vonage number (
-
Test Receiving WhatsApp:
- From your allowlisted WhatsApp number, send a message to the Vonage Sandbox WhatsApp number (
VONAGE_WHATSAPP_NUMBER). - Check: Watch the Node.js console for the "Inbound Message Received" log from the WhatsApp channel. Verify signature verification.
- From your allowlisted WhatsApp number, send a message to the Vonage Sandbox WhatsApp number (
Expected response times:
- SMS delivery: 1-5 seconds typically; up to 30 seconds in congested networks
- WhatsApp delivery: 2-10 seconds typically; up to 60 seconds if recipient is offline
- Webhook arrival: 0.5-3 seconds after message events
- Status updates: Delivered status usually arrives within 3-10 seconds of actual delivery
- Delays beyond these ranges may indicate network issues, carrier problems, or incorrect configuration
5. Security Best Practices for Vonage Webhooks
- API Credentials: Never commit your
.envfile orprivate.keyto version control. Use environment variable management systems provided by your deployment platform in production. - Webhook Security: The signature verification (
verifyVonageSignaturemiddleware) is essential. Vonage uses JWT Bearer Authorization with HMAC-SHA256 signatures for webhooks. Key security practices:- Always verify the JWT token using your signature secret (minimum 32 bits recommended)
- JWT signatures expire 5 minutes after issuance – ensure server time is synchronized using Network Time Protocol (NTP)
- Use HTTPS for all webhook endpoints (TLS prevents man-in-the-middle attacks)
- Optionally verify the
payload_hashclaim in the JWT to ensure the webhook body hasn't been tampered with - The signature verification prevents attackers from sending fake requests to your webhook endpoints
- Input Validation: The example includes basic validation for the
/send-messageendpoint. In a production application, implement more robust validation (e.g., checking phone number formats, message length limits) for both API inputs and potentially webhook payloads before processing. Consider using validation libraries likejoiorexpress-validator. - Rate Limiting: To prevent abuse, consider adding rate limiting to your API endpoints (like
/send-message) using libraries likeexpress-rate-limit. Example configuration:javascriptconst rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again later.' }); app.use('/send-message', limiter); - Error Handling: The provided error handling is basic. Implement more detailed logging and potentially use error tracking services (like Sentry) in production.
- OWASP Best Practices: Follow OWASP Top 10 security guidelines: sanitize inputs, use parameterized queries for databases, implement proper session management, keep dependencies updated, and conduct regular security audits.
6. Troubleshooting Common Vonage Integration Issues
- Ngrok Issues: Ensure ngrok is running and the URL hasn't expired (free accounts have session limits). Double-check the URL matches exactly in your Vonage Application and Sandbox webhook settings (HTTPS).
- Webhook Not Triggering:
- Verify ngrok and URL configuration.
- Check if the Vonage number is correctly linked to the Vonage Application.
- Ensure the Node.js server is running without errors.
- Check the ngrok web interface (usually
http://127.0.0.1:4040) for incoming requests and potential errors.
- Signature Verification Failing:
- Ensure
VONAGE_API_SIGNATURE_SECRETin.envis correct and matches the one in Vonage Dashboard settings. - Make sure the
verifySignaturefunction receives the request body exactly as Vonage sent it. Middleware order can sometimes affect this, butexpress.json()before the verifier is generally fine. Ensure no other middleware is unexpectedly modifying the raw body before verification. - Check for typos in the header extraction (
Authorization: Bearer <token>).
- Ensure
- Message Sending Errors:
- Check console logs for specific error messages from the Vonage SDK.
- Verify
VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_IDare correct. - Ensure
private.keyexists at the specified path and is readable by the Node process. - Check recipient number format (country code, no '+').
- For WhatsApp, ensure the recipient number is allowlisted in the Sandbox, and you are using the correct Sandbox
fromnumber.
401 UnauthorizedErrors (Sending): Usually indicates problems with API Key/Secret or Application ID/Private Key authentication. Double-check these values and theprivate.keyfile path and content.- No
200 OKon Webhooks: If your webhook endpoints don't consistently return a200 OKstatus quickly, Vonage will retry, leading to duplicate logs/processing. Ensure your webhook logic is fast or defers long-running tasks. - WhatsApp Sandbox Limitations: The Sandbox is for testing only and allows up to 5 allowlisted phone numbers. For production, you need: (1) A Meta-verified WhatsApp Business Account (WABA), (2) An approved phone number (not connected to personal WhatsApp), (3) Meta Business Manager verification (typically takes 2-7 business days, up to 14 days during high-volume periods), (4) Industry eligibility (WhatsApp blocks gambling, alcohol, adult content, and other prohibited industries), and (5) Compliance with WhatsApp Commerce policies. Businesses with unverified Business Managers are limited to 2 phone numbers; verified accounts can have up to 20. Messages outside the 24-hour customer service window require pre-approved message templates.
Common Vonage API Error Codes:
| Code | Description | Resolution |
|---|---|---|
| 1000 | Throttled - exceeded submission capacity | Wait and retry; implement exponential backoff |
| 1020 | Invalid params - one or more parameters invalid | Validate phone number format, message content |
| 1022 | Invalid template or template parameters | Check template name/namespace, verify parameters match |
| 1070 | Partner quota exceeded - insufficient credit | Add credit to Vonage account |
| 1120 | Illegal Sender Address rejected | Use approved sender IDs per country regulations |
| 1180 | Absent Subscriber Temporary - device unavailable | Retry later (temporary failure) |
| 1190 | Absent Subscriber Permanent - number inactive | Remove number from database |
| 1240 | Illegal Number - user sent STOP opt-out | Respect opt-out; do not contact again |
| 1330 | Unknown error from carrier | Verify recipient number validity |
For complete error codes, see Vonage Messages API Error Reference.
7. Production Deployment Guide for Vonage Applications
- Hosting: Deploy this Node.js application to a cloud provider (like Heroku, AWS EC2/Lambda, Google Cloud Run, DigitalOcean App Platform). You cannot use ngrok for production.
- Public URL: Your deployment platform will provide a stable public URL for your application. Update the Vonage Application and Sandbox webhook URLs to point to this production URL.
- Environment Variables: Use your hosting provider's mechanism for managing environment variables securely (do not hardcode them or include
.envin your deployment package). - Process Management: Use a process manager like
pm2to keep your Node.js application running reliably in production. - Logging: Implement structured logging (e.g., using Winston or Pino) and send logs to a centralized logging service for monitoring and analysis.
- Database: For storing message history, user data, or application state, integrate a database (e.g., PostgreSQL, MongoDB).
- CI/CD: Set up a Continuous Integration/Continuous Deployment pipeline to automate testing and deployment.
- Scaling Considerations:
- Horizontal scaling: Deploy multiple Node.js instances behind a load balancer (ALB on AWS, Cloud Load Balancing on GCP)
- Vertical scaling: Increase CPU/memory for individual instances handling high throughput
- Load balancing: Use sticky sessions if maintaining WebSocket connections; stateless webhooks don't require session affinity
- Message queue: Implement Redis or RabbitMQ for queuing outbound messages during traffic spikes
- Rate limiting: Vonage allows ~20 msg/sec default; request higher limits or implement client-side throttling
- Monitoring and Observability:
- Application monitoring: Use New Relic, Datadog, or AWS CloudWatch for performance metrics
- Uptime monitoring: Set up Pingdom or UptimeRobot to check webhook endpoint availability
- Error tracking: Implement Sentry or Rollbar for real-time error alerts
- Log aggregation: Use ELK Stack (Elasticsearch, Logstash, Kibana) or CloudWatch Logs
- Alerting: Configure alerts for: webhook failures (>5% error rate), message delivery failures (>10% fail rate), JWT signature failures, API quota exhaustion
- Docker Containerization Example:
Build:dockerfile
FROM node:22-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 3000 CMD ["node", "index.js"]docker build -t vonage-messaging-app .Run:docker run -p 3000:3000 --env-file .env vonage-messaging-app
Frequently Asked Questions
What Node.js version do I need for Vonage Messages API?
Node.js 22 or higher is recommended. Node.js 22 is the current LTS version for 2025, providing Active LTS support until October 2025 and Maintenance LTS until April 2027, making it the best choice for production applications.
How do I verify Vonage webhooks securely?
Vonage uses JWT Bearer Authorization with HMAC-SHA256 signatures. Verify webhooks by extracting the JWT token from the Authorization header, using the verifySignature function from @vonage/jwt with your signature secret (minimum 32 bits). JWT signatures expire after 5 minutes, so synchronize your server time using NTP.
Can I send WhatsApp messages without a WhatsApp Business Account?
Yes, for testing. Vonage provides a Messages API Sandbox that allows you to send free test messages to up to 5 allowlisted phone numbers. For production, you need a Meta-verified WhatsApp Business Account (WABA), an approved phone number, Meta Business Manager verification, and compliance with WhatsApp Commerce policies.
What are the WhatsApp production requirements?
For production WhatsApp messaging, you need: (1) A verified Meta Business Manager account, (2) A dedicated phone number not connected to personal WhatsApp, (3) WABA approval from Meta (typically takes 2-7 business days, up to 14 days during peak periods), (4) Compliance with WhatsApp Commerce policies, and (5) Industry eligibility verification. WhatsApp restricts API access for gambling, alcohol, adult content, and other prohibited industries.
How do I receive inbound SMS and WhatsApp messages?
Configure webhook URLs in your Vonage Application settings. When Vonage receives an inbound message for your linked number, it sends the data via HTTP POST to your Inbound URL. Use ngrok during development to expose your local server, and ensure you return a 200 OK status to prevent webhook retries.
What's the difference between @vonage/server-sdk and @vonage/messages?
@vonage/server-sdk (version 3.24.1+) provides core Vonage API functionality, while @vonage/messages (version 1.20.3+) contains specific message type helpers like WhatsAppText. Install both for full Messages API support.
Why do I need a signature secret?
The signature secret verifies that incoming webhook requests genuinely originated from Vonage, preventing attackers from sending fake requests to your webhook endpoints. Always verify JWT tokens in production using your signature secret from the Vonage Dashboard settings.
How many phone numbers can I use with WhatsApp Business API?
Businesses with unverified Meta Business Manager accounts are limited to 2 phone numbers across all WABAs. Verified Meta Business Manager accounts can have up to 20 phone numbers. The Sandbox allows testing with 5 allowlisted numbers only.
What happens if my webhook doesn't return 200 OK?
If your webhook endpoints don't consistently return a 200 OK status quickly, Vonage will retry sending the webhook, leading to duplicate logs and processing. Ensure your webhook logic is fast or defers long-running tasks to background jobs.
Can I use ngrok in production?
No. ngrok is only for development. Production deployments require a stable, publicly accessible URL from your hosting provider (Heroku, AWS, Google Cloud Run, DigitalOcean, etc.). Update your Vonage Application and Sandbox webhook URLs to point to your production URL.
What are the WhatsApp message template categories and pricing?
As of July 2025, WhatsApp uses per-message pricing with three template categories: (1) Utility templates (transaction confirmations, order updates), (2) Authentication templates (OTP codes, verification), and (3) Marketing templates (promotional offers, campaigns). Each category has different pricing per country. Free-form messages (no template required) are allowed within 24-hour customer service windows. See Vonage WhatsApp pricing for current rates.
How does the WhatsApp 24-hour window work?
You may reply to a user message without a message template within 24 hours of their last message. The window opens when: (1) a user sends a message to your business, or (2) a user replies to your template message. Outside this window, you can only send pre-approved message templates. See WhatsApp Business Messaging Policy for details.
Conclusion
You have successfully built a Node.js Express application capable of sending and receiving both SMS and WhatsApp messages using the Vonage Messages API. You learned how to configure Vonage, handle webhooks securely with signature verification, and implemented the core logic for sending messages via different channels.
This foundation enables you to build more complex communication features, such as automated replies, interactive bots, notification systems, and two-factor authentication flows, reaching users on the channels they prefer.
Next Steps:
- Integrate a database to store message history.
- Build a frontend interface to interact with the sending API.
- Implement more sophisticated logic in the inbound webhook (e.g., chatbots, keyword responses).
- Explore sending other message types supported by the Messages API (images, audio, templates).
- Set up a production WhatsApp Business Account for live deployment.
- Implement robust monitoring and alerting.
- Review Vonage Messages API documentation for advanced features
- Join Vonage Developer Community for support and updates
- Explore Vonage Code Snippets for additional examples
Additional Resources: