This guide provides a step-by-step walkthrough for building a Node.js application using the Express framework to send SMS and WhatsApp messages via the Vonage Messages API. We will cover 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 will 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 We'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.env
file intoprocess.env
.ngrok
: 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.
1. Setting up the project
Let's initialize our 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:
mkdir vonage-node-messaging cd vonage-node-messaging
-
Initialize Node.js Project: Initialize the project using npm. The
-y
flag accepts default settings.npm init -y
This creates a
package.json
file. -
Install Dependencies: Install Express, the Vonage SDKs, and
dotenv
:npm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenv
express
: 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 verification.dotenv
: For managing environment variables.
-
Create Project Structure: Create a basic structure for your code:
mkdir src touch src/server.js touch .env touch .gitignore
src/server.js
: Our main application code..env
: Stores sensitive credentials and configuration..gitignore
: Prevents committing sensitive files (like.env
) andnode_modules
to version control.
-
Configure
.gitignore
: Add the following lines to your.gitignore
file to avoid committing sensitive information and dependencies:# Dependencies node_modules/ # Environment Variables .env # Vonage Private Key private.key # Operating System Files .DS_Store Thumbs.db
-
Set up Vonage Application: You need 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.key
file. Save this file securely in the root of your project directory (e.g., alongsidepackage.json
). Crucially, ensureprivate.key
is listed in your.gitignore
file 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. We'll 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, you need to 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 (we'll use 3000):# Make sure you've authenticated ngrok if it's your first time # ngrok config add-authtoken YOUR_AUTH_TOKEN ngrok http 3000
ngrok
will 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
ngrok
HTTPS URL followed by/webhooks/inbound
into the Inbound URL field (e.g.,https://<random-string>.ngrok-free.app/webhooks/inbound
). - Enter your
ngrok
HTTPS URL followed by/webhooks/status
into 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/inbound
and.../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.env
file you created and add the following variables, replacing the placeholder values with your actual credentials:# 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=3000
VONAGE_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.key
file 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 thengrok
port).
2. Implementing core functionality
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
Vonage
client using credentials from environment variables. Checks ensure essential credentials (including signature secret) exist, exiting if not. We comment out theapiHost
override for the sandbox, preferring the default endpoint. sendSms
Function: TakestoNumber
andmessageText
, constructs the payload for the Messages API (channel: 'sms'
), and usesvonage.messages.send()
. Basic logging for success and failure is included.sendWhatsApp
Function: Similar tosendSms
, but uses theWhatsAppText
class from@vonage/messages
and specifieschannel: 'whatsapp'
. It uses theVONAGE_WHATSAPP_SANDBOX_NUMBER
by default.rawBodyMiddleware
&verifyWebhookSignature
: This is crucial for security. Vonage signs webhook requests with a JWT using your Signature Secret.verifySignature
needs the raw, unparsed request body. We create middleware (rawBodyMiddleware
) usingexpress.raw
to capture this raw body beforeexpress.json()
parses it for specific webhook routes. TheverifyWebhookSignature
function extracts the JWT from theAuthorization
header and uses@vonage/jwt
'sverifySignature
method 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
rawBodyMiddleware
first, thenexpress.json()
. - They must call
verifyWebhookSignature
to ensure the request is legitimate. Unauthorized requests are rejected with401
. - The
/inbound
handler logs the incoming message, extracts sender and content based on the channel, and optionally sends a reply using our helper functions. - The
/status
handler 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
sendSms
orsendWhatsApp
functions. - Note: These endpoints lack proper authentication/authorization, which is essential for production (see Section 3). The
@api
comments 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.
3. Building a complete API layer
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 likepassport
withpassport-jwt
orpassport-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):
const 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) => { /* ... */ });
- API Keys: Issue unique keys to clients. Clients send the key in a header (e.g.,
-
Request Validation: Sanitize and validate all incoming data rigorously.
- Use libraries like
express-validator
orjoi
to 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
:npm install express-validator libphonenumber-js
const { 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) ... } );
- Use libraries like