messaging channels
messaging channels
Send SMS and WhatsApp Messages with Node.js, Express, and Vonage
Build a Node.js Express application to send SMS and WhatsApp messages using the Vonage Messages API. Includes webhook handling, signature verification, and production security best practices.
Build a Node.js Express application to send SMS and WhatsApp messages via the Vonage Messages API. This guide covers project setup, sending messages through different channels, handling incoming messages and status updates via webhooks, and implementing essential production considerations like security and error handling.
By the end of this tutorial, you'll have a functional application capable of programmatically sending SMS and WhatsApp messages and receiving delivery status updates and inbound messages from users. This provides a foundation for building chatbots, notification systems, two-factor authentication flows, and other communication-driven features.
Project Overview and Goals
What You'll Build:
- A Node.js Express server
- Integration with the Vonage Messages API using the official Node.js SDK
- Functionality to send outbound SMS messages
- Functionality to send outbound WhatsApp messages (using the Vonage Sandbox initially)
- Webhook endpoints to receive inbound messages from users (SMS and WhatsApp)
- Webhook endpoints to receive message status updates (e.g., delivered, failed)
- Secure handling of API credentials and webhook signature verification
Problem Solved:
This application provides a unified way to interact with customers or users over two popular messaging channels – SMS and WhatsApp – directly from your Node.js backend. It abstracts the complexities of the Vonage API into reusable functions and provides a basic structure for handling asynchronous communication events.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications (Version 18 or higher recommended)
- Express: A minimal and flexible Node.js web application framework used to build the server and API endpoints/webhooks
- Vonage Messages API: A powerful API enabling communication across multiple channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber) through a single interface
- Vonage Node.js Server SDK (
@vonage/server-sdk): Simplifies interaction with Vonage APIs, including authentication and sending messages - Vonage Messages SDK (
@vonage/messages): Provides convenient classes for constructing messages for specific channels (likeWhatsAppText) - Vonage JWT SDK (
@vonage/jwt): Used for verifying the signature of incoming webhook requests to ensure they originate from Vonage dotenv: A module to load environment variables from a.envfile intoprocess.envngrok: A tool to expose local servers to the internet, necessary for testing webhooks during development
System Architecture:
+-----------------+ HTTP Request +---------------------+ Vonage API Call +-----------------+
| Your Application| ---------------------> | Node.js/Express App | -------------------------> | Vonage Platform |
| (e.g., Frontend)| (API Call) | (Vonage SDK) | (Messages API) | |
+-----------------+ +---------------------+ +-------+---------+
| |
| Webhook Call (POST) | Send Message
V V
+-----------------+ Webhook Call (POST) +-------+---------+ +-------+---------+
| Vonage Platform | <----------------------- | Node.js/Express App | | User's Device |
| | (Inbound Msg / Status) | (Webhook Handler) | | (SMS/WhatsApp) |
+-----------------+ +---------------------+ +-----------------+Prerequisites:
- Vonage API Account: Sign up for free at Vonage API Dashboard. You get free credit to start.
- Node.js and npm: Install Node.js (v18 or higher recommended, includes npm) from nodejs.org.
- ngrok: Install ngrok from ngrok.com and create a free account to authenticate your agent.
- A Vonage Phone Number: Purchase an SMS-capable virtual number from the Vonage Dashboard (Numbers → Buy numbers).
- WhatsApp Enabled Device: A smartphone with WhatsApp installed for testing.
How Do You Set Up a Node.js Project for Vonage Messaging?
Initialize your Node.js project 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:
bashmkdir vonage-node-messaging cd vonage-node-messaging -
Initialize Node.js Project: Initialize the project using npm. The
-yflag accepts default settings.bashnpm init -yThis creates a
package.jsonfile. -
Install Dependencies: Install Express, the Vonage SDKs, and
dotenv:bashnpm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenvexpress: Web framework@vonage/server-sdk: Core Vonage SDK for authentication and API calls@vonage/messages: Specific classes for constructing message types (e.g.,WhatsAppText)@vonage/jwt: For webhook signature verificationdotenv: For managing environment variables
-
Create Project Structure: Create a basic structure for your code:
bashmkdir src touch src/server.js touch .env touch .gitignoresrc/server.js: Your main application code.env: Stores sensitive credentials and configuration.gitignore: Prevents committing sensitive files (like.env) andnode_modulesto version control
-
Configure
.gitignore: Add the following lines to your.gitignorefile to avoid committing sensitive information and dependencies:text# Dependencies node_modules/ # Environment Variables .env # Vonage Private Key private.key # Operating System Files .DS_Store Thumbs.db -
Set up Vonage Application: Create a Vonage Application to group your numbers and configurations, and generate authentication credentials.
- Navigate to Applications in your Vonage API Dashboard.
- Click + Create a new application.
- Give your application a name (e.g., "Node Express Messaging").
- Click Generate public and private key. This will automatically download a
private.keyfile. Save this file securely in the root of your project directory (e.g., alongsidepackage.json). Crucially, ensureprivate.keyis listed in your.gitignorefile and is NEVER committed to version control. For production, load this key securely (e.g., from environment variables, secrets management, or a secure volume mount) rather than directly from the filesystem. The public key is stored by Vonage. - Enable the Messages capability by toggling it on.
- You'll see fields for Inbound URL and Status URL. Fill these in the next step after starting
ngrok. Leave them blank for now or use temporary placeholders likehttp://localhost. - Click Generate new application.
- On the next screen, Link the Vonage virtual number you purchased earlier to this application. Find your number and click the "Link" button.
- Note down the Application ID displayed on this page.
-
Configure SMS API Settings: Ensure your account uses the Messages API for SMS by default, as recommended by Vonage documentation for new integrations.
- In the Vonage Dashboard, navigate to API Settings.
- Scroll down to the SMS Settings section.
- Under Default SMS Setting, select Messages API.
- Click Save changes.
-
Set up WhatsApp Sandbox (for Testing): For testing WhatsApp without a full business setup, use the Vonage Sandbox.
- Navigate to Messages API Sandbox in the Vonage Dashboard.
- Follow the instructions to activate the Sandbox by sending a specific message from your WhatsApp number to the provided Sandbox number. This "allowlists" your number for testing.
- Keep this page open, as you'll need the Sandbox number and will configure webhooks here too.
-
Start
ngrok: To receive webhooks from Vonage on your local machine, expose your local server to the internet.-
Open a new terminal window (keep your project terminal open).
-
Run
ngrok, specifying the port your Express server will listen on (use port 3000):bash# Make sure you've authenticated ngrok if it's your first time # ngrok config add-authtoken YOUR_AUTH_TOKEN ngrok http 3000 -
ngrokwill display output including a Forwarding URL that looks likehttps://<random-string>.ngrok-free.app(or similar, depending on your plan/version). Copy this HTTPS URL.
-
-
Configure Webhook URLs in Vonage:
- Vonage Application: Go back to your application settings in the Vonage Dashboard (Applications → Your Application Name).
- Enter your
ngrokHTTPS URL followed by/webhooks/inboundinto the Inbound URL field (e.g.,https://<random-string>.ngrok-free.app/webhooks/inbound). - Enter your
ngrokHTTPS URL followed by/webhooks/statusinto the Status URL field (e.g.,https://<random-string>.ngrok-free.app/webhooks/status). - Click Save changes.
- Enter your
- WhatsApp Sandbox: Go back to the Messages API Sandbox page in the Vonage Dashboard.
- Enter the same URLs (
.../webhooks/inboundand.../webhooks/status) into the respective webhook fields on the Sandbox configuration page. - Click Save webhooks.
- Enter the same URLs (
- Vonage Application: Go back to your application settings in the Vonage Dashboard (Applications → Your Application Name).
-
Configure Environment Variables (
.env): Open the.envfile you created and add the following variables, replacing the placeholder values with your actual credentials:dotenv# Vonage API Credentials VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Credentials VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_PRIVATE_KEY=./private.key # Path relative to project root – load securely in production! # Vonage Numbers VONAGE_SMS_FROM_NUMBER=YOUR_VONAGE_SMS_NUMBER # Your purchased Vonage number in E.164 format (e.g., 14155550100) VONAGE_WHATSAPP_SANDBOX_NUMBER=14157386102 # The Vonage WhatsApp Sandbox number # Webhook Security VONAGE_API_SIGNATURE_SECRET=YOUR_SIGNATURE_SECRET # Server Configuration PORT=3000VONAGE_API_KEY&VONAGE_API_SECRET: Found at the top of your Vonage API Dashboard.VONAGE_APPLICATION_ID: The ID generated when you created the Vonage Application.VONAGE_PRIVATE_KEY: The path to theprivate.keyfile you downloaded and saved in your project root. Ensure this path is correct relative to where you run the node process.VONAGE_SMS_FROM_NUMBER: Your purchased Vonage virtual number (without+or leading00).VONAGE_WHATSAPP_SANDBOX_NUMBER: The specific number provided by the Vonage WhatsApp Sandbox. For production WhatsApp, this would be your registered WhatsApp Business number.VONAGE_API_SIGNATURE_SECRET: Found in the Vonage API Dashboard under API Settings → Webhook signature secret. Click "Edit" or "Generate" if needed.PORT: The port your Express server will run on (must match thengrokport).
How Do You Implement SMS and WhatsApp Messaging in Express?
Now, let's write the code in src/server.js to initialize the server, configure Vonage, send messages, and handle webhooks.
// src/server.js
require('dotenv').config(); // Load environment variables from .env file first
const express = require('express');
const { Vonage } = require('@vonage/server-sdk');
const { WhatsAppText } = require('@vonage/messages');
const { verifySignature } = require('@vonage/jwt');
// --- Initialization ---
const app = express();
// Note: We apply express.json() globally AFTER raw body middleware for webhook routes
// app.use(express.json()); // Moved lower
app.use(express.urlencoded({ extended: true })); // Middleware to parse URL-encoded bodies
// Initialize Vonage Client
// Ensure all required environment variables are loaded correctly
if (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET || !process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_PRIVATE_KEY) {
console.error('ERROR: Missing Vonage API credentials in .env file.');
process.exit(1); // Exit if essential credentials are missing
}
if (!process.env.VONAGE_API_SIGNATURE_SECRET) {
// Signature secret is mandatory for webhook security.
console.error('ERROR: Missing VONAGE_API_SIGNATURE_SECRET in .env file. Webhooks cannot be verified.');
process.exit(1);
}
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_PRIVATE_KEY // Path to your private key file
},
{
// Optional: Set custom logger or other options if needed
// logger: customLogger,
// Optional: Use the sandbox host for ALL messages if testing ONLY WhatsApp Sandbox features extensively
// apiHost: 'https://messages-sandbox.nexmo.com' // NOTE: Remove/comment this for production SMS or non-sandbox WhatsApp
});
// --- Helper Functions ---
/**
* Sends an SMS message using the Vonage Messages API.
* @param {string} toNumber - The recipient's phone number in E.164 format.
* @param {string} messageText - The text content of the message.
*/
async function sendSms(toNumber, messageText) {
console.log(`Attempting to send SMS to ${toNumber}...`);
if (!process.env.VONAGE_SMS_FROM_NUMBER) {
console.error('ERROR: VONAGE_SMS_FROM_NUMBER is not set in .env');
return;
}
try {
const resp = await vonage.messages.send({
message_type: "text",
text: messageText,
to: toNumber,
from: process.env.VONAGE_SMS_FROM_NUMBER,
channel: "sms"
});
console.log(`SMS sent successfully to ${toNumber}. Message UUID: ${resp.messageUuid}`);
} catch (err) {
console.error(`Error sending SMS to ${toNumber}:`, err?.response?.data || err.message);
// Consider adding more robust error handling/retry logic here
}
}
/**
* Sends a WhatsApp message using the Vonage Messages API.
* Uses the Sandbox number by default.
* @param {string} toNumber - The recipient's WhatsApp-enabled number in E.164 format (must be allowlisted in Sandbox).
* @param {string} messageText - The text content of the message.
*/
async function sendWhatsApp(toNumber, messageText) {
console.log(`Attempting to send WhatsApp message to ${toNumber} via Sandbox...`);
if (!process.env.VONAGE_WHATSAPP_SANDBOX_NUMBER) {
console.error('ERROR: VONAGE_WHATSAPP_SANDBOX_NUMBER is not set in .env');
return;
}
try {
// NOTE: For production, 'from' would be your registered WhatsApp Business number.
// The host might also need adjustment if not using the global endpoint.
// The 'apiHost' in the Vonage client initialization can also control this.
const resp = await vonage.messages.send(
new WhatsAppText({
text: messageText,
to: toNumber,
from: process.env.VONAGE_WHATSAPP_SANDBOX_NUMBER,
// Optional: client_ref for your own tracking
// client_ref: `my-whatsapp-message-${Date.now()}`
})
// Optional second argument for sandbox override if client wasn't initialized with sandbox host:
// , { apiHost: 'https://messages-sandbox.nexmo.com' }
);
console.log(`WhatsApp message sent successfully to ${toNumber}. Message UUID: ${resp.messageUuid}`);
} catch (err) {
console.error(`Error sending WhatsApp message to ${toNumber}:`, err?.response?.data || err.message);
// Consider adding more robust error handling/retry logic here
}
}
/**
* Verifies the signature of an incoming Vonage webhook request.
* @param {express.Request} req - The Express request object.
* @returns {boolean} - True if the signature is valid, false otherwise.
*/
function verifyWebhookSignature(req) {
try {
// IMPORTANT: Use raw body for signature verification
// We need to configure Express to make the raw body available.
// See rawBodyMiddleware configuration below.
if (!req.rawBody) {
console.error('ERROR: Raw body not available for signature verification. Ensure rawBodyMiddleware is used.');
return false;
}
if (!process.env.VONAGE_API_SIGNATURE_SECRET) {
// This should ideally be caught at startup, but double-check here.
console.error('ERROR: VONAGE_API_SIGNATURE_SECRET is not set. Cannot verify webhook signature.');
// Fail verification if the secret is missing. Verification is mandatory for security.
return false;
}
// Clone headers and convert to lowercase keys as expected by verifySignature
const headers = {};
for (const key in req.headers) {
headers[key.toLowerCase()] = req.headers[key];
}
// Extract token from Authorization header (format: ""Bearer <JWT>"")
const token = headers['authorization']?.split(' ')[1];
if (!token) {
console.error('Webhook Error: Authorization header missing or malformed.');
return false;
}
// Verify the signature using the raw body
const isValid = verifySignature(token, process.env.VONAGE_API_SIGNATURE_SECRET, req.rawBody.toString());
if (!isValid) {
console.error('Webhook Error: Invalid Signature');
}
return isValid;
} catch (error) {
console.error('Error verifying webhook signature:', error.message);
return false;
}
}
// --- Middleware for Raw Body ---
// This needs to run BEFORE express.json() for routes requiring signature verification
const rawBodyMiddleware = express.raw({
type: 'application/json',
verify: (req, res, buf) => {
// Store the raw buffer on the request object
req.rawBody = buf;
}
});
// --- Webhook Endpoints ---
// Apply raw body parsing ONLY to webhook routes, THEN apply JSON parsing
app.post('/webhooks/inbound', rawBodyMiddleware, express.json(), async (req, res) => {
console.log('Received Inbound Webhook:');
console.log(JSON.stringify(req.body, null, 2)); // Log the full body
// 1. Verify Signature (CRITICAL for security)
if (!verifyWebhookSignature(req)) {
console.log('Webhook signature verification failed. Responding 401.');
return res.status(401).send('Invalid Signature');
}
console.log('Webhook signature verified successfully.');
// 2. Process the message based on channel
const { from, channel, message } = req.body;
if (channel === 'sms' || channel === 'whatsapp') {
const sender = from.number || from.id; // WhatsApp uses 'id', SMS uses 'number'
const messageContent = message?.content?.text || '[Non-text message content]';
console.log(`Received ${channel.toUpperCase()} message from ${sender}: ""${messageContent}""`);
// 3. (Optional) Send an automated reply
const replyText = `Thanks for your ${channel.toUpperCase()} message! We received: ""${messageContent}""`;
if (channel === 'sms' && process.env.VONAGE_SMS_FROM_NUMBER) {
// IMPORTANT: Check if 'from.number' exists before replying to SMS
if (from.number) {
await sendSms(from.number, replyText);
} else {
console.warn('Cannot reply to SMS: Sender number missing in webhook payload.');
}
} else if (channel === 'whatsapp' && process.env.VONAGE_WHATSAPP_SANDBOX_NUMBER) {
// IMPORTANT: Check if 'from.id' exists before replying to WhatsApp
if (from.id) {
// Check if within 24-hour window for non-template messages (Sandbox usually allows replies)
await sendWhatsApp(from.id, replyText);
} else {
console.warn('Cannot reply to WhatsApp: Sender ID missing in webhook payload.');
}
}
} else {
console.log(`Received webhook for unhandled channel: ${channel}`);
}
// 4. Acknowledge receipt with a 200 OK
// Vonage webhooks expect a 200 OK response quickly, otherwise they retry.
res.status(200).end();
});
app.post('/webhooks/status', rawBodyMiddleware, express.json(), (req, res) => {
console.log('Received Status Webhook:');
console.log(JSON.stringify(req.body, null, 2)); // Log the full body
// 1. Verify Signature (CRITICAL for security)
if (!verifyWebhookSignature(req)) {
console.log('Webhook signature verification failed. Responding 401.');
return res.status(401).send('Invalid Signature');
}
console.log('Webhook signature verified successfully.');
// 2. Process the status update (e.g., update database, trigger alerts)
const { message_uuid, status, timestamp, error } = req.body;
console.log(`Status update for Message UUID ${message_uuid}: ${status} at ${timestamp}`);
if (error) {
console.error(`Error details: Code=${error.code}, Reason=${error.reason}`);
}
// 3. Acknowledge receipt with a 200 OK
res.status(200).end();
});
// --- API Endpoints (Example) ---
// Apply standard JSON parsing middleware to API routes (and any other non-webhook routes)
app.use(express.json());
// Note: The following comments use apidoc syntax (https://apidocjs.com/).
// You can use the 'apidoc' tool to generate API documentation from these comments.
/**
* @api {post} /api/send/sms Send an SMS message
* @apiName SendSms
* @apiGroup Messages
*
* @apiBody {String} to Recipient phone number in E.164 format.
* @apiBody {String} message Text content of the SMS.
*
* @apiSuccess {String} status Success message.
* @apiError {String} error Error description.
*/
app.post('/api/send/sms', async (req, res) => {
const { to, message } = req.body;
// Basic Input Validation
if (!to || !message) {
return res.status(400).json({ error: 'Missing ""to"" or ""message"" in request body.' });
}
// Basic format check (11-15 digits). For robust E.164 validation, use a library like libphonenumber-js.
if (!/^\d{11,15}$/.test(to)) {
return res.status(400).json({ error: 'Invalid ""to"" number format. The Vonage SDK expects E.164 format without the leading \'+\'.' });
}
// TODO: Add Authentication/Authorization middleware here for production
console.log(`API request to send SMS to ${to}`);
try {
await sendSms(to, message);
res.status(200).json({ status: `SMS dispatch initiated to ${to}` });
} catch (error) {
console.error(""API Error sending SMS:"", error);
res.status(500).json({ error: 'Failed to send SMS.' });
}
});
/**
* @api {post} /api/send/whatsapp Send a WhatsApp message (via Sandbox)
* @apiName SendWhatsApp
* @apiGroup Messages
*
* @apiBody {String} to Recipient WhatsApp number in E.164 format (must be allowlisted).
* @apiBody {String} message Text content of the message.
*
* @apiSuccess {String} status Success message.
* @apiError {String} error Error description.
*/
app.post('/api/send/whatsapp', async (req, res) => {
const { to, message } = req.body;
// Basic Input Validation
if (!to || !message) {
return res.status(400).json({ error: 'Missing ""to"" or ""message"" in request body.' });
}
// Basic format check (11-15 digits). For robust E.164 validation, use a library like libphonenumber-js.
if (!/^\d{11,15}$/.test(to)) {
return res.status(400).json({ error: 'Invalid ""to"" number format. The Vonage SDK expects E.164 format without the leading \'+\'.' });
}
// TODO: Add Authentication/Authorization middleware here for production
console.log(`API request to send WhatsApp to ${to}`);
try {
await sendWhatsApp(to, message); // Using the Sandbox number by default
res.status(200).json({ status: `WhatsApp dispatch initiated to ${to}` });
} catch (error) {
console.error(""API Error sending WhatsApp:"", error);
res.status(500).json({ error: 'Failed to send WhatsApp message.' });
}
});
// --- Server Start ---
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
console.log(`ngrok forwarding URL (ensure it matches Vonage webhook config): Check your ngrok terminal.`);
// You can add a startup check here to ensure essential env vars are present
if (!process.env.VONAGE_SMS_FROM_NUMBER) console.warn('WARN: VONAGE_SMS_FROM_NUMBER not set. Cannot send SMS.');
if (!process.env.VONAGE_WHATSAPP_SANDBOX_NUMBER) console.warn('WARN: VONAGE_WHATSAPP_SANDBOX_NUMBER not set. Cannot send WhatsApp via Sandbox.');
// Note: VONAGE_API_SIGNATURE_SECRET absence is now a fatal error at startup.
});Explanation:
- Dependencies & Setup: We import necessary modules and initialize Express.
dotenv.config()loads variables from.env. - Vonage Client: We initialize the
Vonageclient using credentials from environment variables. Checks ensure essential credentials (including signature secret) exist, exiting if not. We comment out theapiHostoverride for the sandbox, preferring the default endpoint. sendSmsFunction: TakestoNumberandmessageText, constructs the payload for the Messages API (channel: 'sms'), and usesvonage.messages.send(). Basic logging for success and failure is included.sendWhatsAppFunction: Similar tosendSms, but uses theWhatsAppTextclass from@vonage/messagesand specifieschannel: 'whatsapp'. It uses theVONAGE_WHATSAPP_SANDBOX_NUMBERby default.rawBodyMiddleware&verifyWebhookSignature: This is crucial for security. Vonage signs webhook requests with a JWT using your Signature Secret.verifySignatureneeds the raw, unparsed request body. We create middleware (rawBodyMiddleware) usingexpress.rawto capture this raw body beforeexpress.json()parses it for specific webhook routes. TheverifyWebhookSignaturefunction extracts the JWT from theAuthorizationheader and uses@vonage/jwt'sverifySignaturemethod along with the raw body and your secret to validate the request. Crucially, if the signature secret is missing or verification fails, the function now returnsfalse, preventing insecure processing.- Webhook Endpoints (
/webhooks/inbound,/webhooks/status):- These routes use the
rawBodyMiddlewarefirst, thenexpress.json(). - They must call
verifyWebhookSignatureto ensure the request is legitimate. Unauthorized requests are rejected with401. - The
/inboundhandler logs the incoming message, extracts sender and content based on the channel, and optionally sends a reply using our helper functions. - The
/statushandler logs delivery status updates. - Both endpoints must respond with
res.status(200).end()promptly to prevent Vonage from retrying the webhook.
- These routes use the
- API Endpoints (
/api/send/sms,/api/send/whatsapp):- Simple POST endpoints are added to trigger sending messages externally. These routes use the standard
express.json()middleware applied after the webhook routes. - They perform basic validation on the request body (
to,message), including a simple digit check for the phone number, with notes on using better validation libraries. The error message clarifies the SDK's expectation for E.164 format. - They call the respective
sendSmsorsendWhatsAppfunctions. - Note: These endpoints lack proper authentication/authorization, which is essential for production (see Section 3). The
@apicomments are noted as usable withapidoc.
- Simple POST endpoints are added to trigger sending messages externally. These routes use the standard
- Server Start: The server starts listening on the configured
PORT. We add checks/warnings on startup for missing optional environment variables.
How Do You Secure and Enhance Your Messaging API for Production?
The example above includes basic API endpoints (/api/send/sms, /api/send/whatsapp). For a production system, you would enhance this layer significantly:
-
Authentication & Authorization: Protect your API endpoints. Common methods include:
-
API Keys: Issue unique keys to clients. Clients send the key in a header (e.g.,
X-API-Key). Your server validates the key against a stored list. -
JWT (JSON Web Tokens): Implement a login flow where users/systems get a JWT upon successful authentication. They include the JWT in the
Authorization: Bearer <token>header for subsequent requests. Libraries likepassportwithpassport-jwtorpassport-http-bearer(for API keys) can help. -
OAuth2: For third-party integrations or more complex authorization scenarios.
-
Implementation Example (Conceptual - using a simple API key check middleware):
javascriptconst VALID_API_KEYS = ['your-super-secret-key-1', 'another-key']; // Store securely, e.g., in DB or vault function authenticateApiKey(req, res, next) { const apiKey = req.headers['x-api-key']; if (apiKey && VALID_API_KEYS.includes(apiKey)) { console.log('API Key authenticated.'); next(); // Key is valid, proceed } else { console.warn('API Key authentication failed.'); res.status(401).json({ error: 'Unauthorized: Invalid or missing API Key' }); } } // Apply middleware to API routes app.post('/api/send/sms', authenticateApiKey, async (req, res) => { /* ... */ }); app.post('/api/send/whatsapp', authenticateApiKey, async (req, res) => { /* ... */ });
-
-
Request Validation: Sanitize and validate all incoming data rigorously.
-
Use libraries like
express-validatororjoito define schemas for request bodies and query parameters. -
Check data types, lengths, formats (e.g., ensure phone numbers adhere to E.164 using libraries like
libphonenumber-js). -
Example using
express-validator:bashnpm install express-validator libphonenumber-jsjavascriptconst { body, validationResult } = require('express-validator'); const { parsePhoneNumberFromString } = require('libphonenumber-js'); app.post('/api/send/sms', authenticateApiKey, // Your auth middleware first // Validation rules body('to').custom(value => { // Note: Vonage SDK expects E.164 without '+', but validation should check standard format. // Prepend '+' for robust parsing if necessary, then remove it for the SDK call. const numberToValidate = value.startsWith('+') ? value : `+${value}`; const phoneNumber = parsePhoneNumberFromString(numberToValidate); if (!phoneNumber || !phoneNumber.isValid()) { throw new Error('Invalid E.164 phone number format required (e.g., +14155550100)'); } // Ensure the original format (without '+') is used later if needed by SDK // req.body.to = value; // Keep original format passed in if it was valid without '+' return true; }), body('message').notEmpty().isLength({ min: 1, max: 1600 }).withMessage('Message is required and must be between 1 and 1600 characters'), async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } // Validation passed, proceed with req.body // Ensure you use the correct format of 'to' for the sendSms function const { to, message } = req.body; // ... call sendSms(to, message) ... } );
-
Frequently Asked Questions
Can I send WhatsApp messages outside the 24-hour window?
WhatsApp enforces a 24-hour customer service window. When a user messages you or calls you, a 24-hour timer starts. During this window, you can send any type of message for free. After 24 hours, you can only send pre-approved message templates. The window resets each time the user responds. Plan your messaging strategy accordingly – use templates to initiate conversations, then engage freely within the 24-hour window.
How do I verify that webhooks are coming from Vonage?
Vonage signs webhook requests with a JWT (JSON Web Token) using HMAC-SHA256 and your signature secret. Always verify the signature before processing webhook data. The signature secret should be at least 32 bits and stored securely. Compare the JWT from the Authorization header against your secret using the @vonage/jwt library's verifySignature method. Additionally, compare a SHA-256 hash of the payload to the payload_hash claim in the JWT to guard against token replay attacks. Reject any requests that fail verification with a 401 status code.
What's the difference between the Vonage Sandbox and production WhatsApp?
The Vonage WhatsApp Sandbox uses a shared test number (14157386102) and allows you to test WhatsApp integration without a full WhatsApp Business Account setup. You must "allowlist" your number by sending a specific message to the Sandbox number. For production, you need a registered WhatsApp Business number linked to your Vonage application. The Sandbox is ideal for development and testing, but production requires proper WhatsApp Business API approval and your own dedicated business phone number.
How do I handle message delivery failures?
Monitor the status webhook (/webhooks/status) for delivery updates. Messages can fail for various reasons: invalid phone numbers, undelivered due to network issues, or rejected by WhatsApp. Implement retry logic with exponential backoff for transient failures. Log all message UUIDs and their status updates to track delivery rates. For critical messages, consider implementing a fallback channel (e.g., SMS) when WhatsApp delivery fails. Always handle errors gracefully in your sendSms and sendWhatsApp functions.
What phone number format does Vonage expect?
Vonage expects phone numbers in E.164 format without the leading + symbol. E.164 format includes the country code followed by the subscriber number (e.g., 14155550100 for a US number). Use libraries like libphonenumber-js to validate and format phone numbers correctly before sending them to the Vonage API. Invalid phone number formats will result in API errors and failed message delivery.
How do I secure my API endpoints for production use?
Never deploy the example endpoints to production without authentication. Implement API key validation, JWT-based authentication, or OAuth2 depending on your use case. Store API keys in a secure database or secrets management service, never in code. Use HTTPS for all communications. Implement rate limiting to prevent abuse. Validate all input data rigorously using libraries like express-validator. Consider implementing IP whitelisting for additional security. Load your Vonage private key from environment variables or a secure vault, never commit it to version control.
Can I send media files via WhatsApp using Vonage?
Yes, the Vonage Messages API supports sending images, videos, audio files, and documents via WhatsApp. Use the appropriate message type classes from @vonage/messages (e.g., WhatsAppImage, WhatsAppVideo, WhatsAppAudio, WhatsAppFile). Media files must be publicly accessible via HTTPS URL, or you can upload them using Vonage's media API. WhatsApp has file size limits: images (5 MB), videos (16 MB), audio (16 MB), and documents (100 MB). The file format must be supported by WhatsApp.
How do I test webhooks locally?
Use ngrok to expose your local Express server to the internet. Run ngrok http 3000 to create a public HTTPS URL that forwards to your local port 3000. Copy the ngrok HTTPS URL and configure it in your Vonage Application settings for both Inbound URL and Status URL (e.g., https://abc123.ngrok-free.app/webhooks/inbound). Ngrok provides a web interface at http://localhost:4040 where you can inspect all webhook requests and responses, making debugging easier.
What are the rate limits for the Vonage Messages API?
Vonage implements rate limiting to ensure fair usage and system stability. The specific limits depend on your account type and the messaging channel. WhatsApp has its own rate limits based on your business tier (Tier 1: 1,000 unique recipients per 24 hours, scaling up with verified quality). Implement proper error handling to catch rate limit errors (HTTP 429) and implement exponential backoff retry logic. Consider implementing a message queue for high-volume applications to smooth out traffic spikes and stay within rate limits.
How do I migrate from the Sandbox to production WhatsApp?
To move to production WhatsApp, register for a WhatsApp Business Account through Vonage. Complete the business verification process with Meta (Facebook). Once approved, you'll receive a dedicated WhatsApp Business number. Update your .env file to use your production WhatsApp number instead of the Sandbox number. Link your production number to your Vonage Application. Update webhook URLs if needed. Test thoroughly in production with real customer opt-ins. Ensure compliance with WhatsApp's Business Policy and Commerce Policy.
Related Resources: