code examples
code examples
Send and Receive SMS and WhatsApp Messages with Node.js and Vonage Messages API
Learn how to build a Node.js Express application to send and receive SMS and WhatsApp messages using the Vonage Messages API. Complete guide with webhooks, security, and deployment.
Send and Receive SMS and WhatsApp Messages with Node.js and Vonage Messages API
Build a Node.js application using Express to send and receive SMS and WhatsApp messages via the Vonage Messages API. This guide covers project setup, configuration, webhook handling, security implementation, and deployment preparation.
By the end of this tutorial, you will have a functional application capable of:
- Sending SMS messages programmatically.
- Sending WhatsApp messages using the Vonage WhatsApp Sandbox for testing.
- Receiving incoming SMS and WhatsApp messages via webhooks.
- Verifying incoming webhook requests for security.
- Handling basic errors and logging essential information.
This enables your application to communicate with users via SMS and WhatsApp for notifications, alerts, two-factor authentication (2FA), customer support, and more – all through a unified API interface.
Technologies used
- Node.js: JavaScript runtime environment for building server-side applications.
- Express: Minimal and flexible Node.js web application framework.
- Vonage Messages API: Unified API for sending and receiving messages across multiple channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber).
- Vonage Node SDK: Simplifies interaction with Vonage APIs in Node.js. This guide uses these SDK modules:
@vonage/server-sdk: Core client initialization and authentication.@vonage/messages: Constructing and sending messages via the Messages API.@vonage/jwt: Verifying signatures of incoming webhooks.
- ngrok: Tool to expose local development servers to the internet for webhook testing.
- dotenv: Module to load environment variables from a
.envfile.
Prerequisites
Before you start, ensure you have:
- Vonage API Account: Sign up for a free Vonage API account. You'll receive free credit to start building.
- Node.js: Install Node.js version 18 or higher from nodejs.org.
- ngrok: Install ngrok and create a free account at ngrok.com.
- WhatsApp Account: Personal WhatsApp account on your smartphone for testing the WhatsApp Sandbox integration.
- Vonage Phone Number: Purchase a virtual phone number from your Vonage dashboard to send and receive SMS. Navigate to Numbers > Buy numbers.
System architecture
Your application interacts with the Vonage platform in two ways:
- Sending Messages: Your Node.js app uses the Vonage SDK to call the Vonage Messages API, specifying the channel (SMS or WhatsApp), recipient, sender ID (your Vonage number or WhatsApp Sandbox number), and message content. Vonage delivers the message via the respective network.
- Receiving Messages: When a user sends a message to your Vonage number (SMS) or the WhatsApp Sandbox number, Vonage sends an HTTP POST request (webhook) containing the message details to a preconfigured URL endpoint hosted by your Node.js application. During development, ngrok forwards these requests from the public internet to your local machine.
graph TD
subgraph Your Application
NodeApp[Node.js/Express App]
EnvFile[.env File] --> NodeApp
end
subgraph Vonage Platform
MessagesAPI[Messages API]
VonageNumber[Vonage SMS Number]
WhatsAppSandbox[WhatsApp Sandbox]
VonageApp[Vonage Application Config]
Webhooks[Webhook Service]
end
subgraph External Networks
SMSNetwork[SMS Network]
WhatsAppNetwork[WhatsApp Network]
end
UserSMS[User (SMS)]
UserWhatsApp[User (WhatsApp)]
NodeApp -- Send Request (SDK) --> MessagesAPI
MessagesAPI -- Deliver SMS --> SMSNetwork --> UserSMS
MessagesAPI -- Deliver WhatsApp --> WhatsAppNetwork --> UserWhatsApp
UserSMS -- Sends SMS --> SMSNetwork -- Inbound SMS --> VonageNumber
UserWhatsApp -- Sends WhatsApp --> WhatsAppNetwork -- Inbound WhatsApp --> WhatsAppSandbox
VonageNumber -- Associated with --> VonageApp
WhatsAppSandbox -- Triggers Webhook --> Webhooks
VonageApp -- Webhook URLs --> Webhooks
Webhooks -- POST Request --> Ngrok[ngrok Tunnel] --> NodeApp
style UserSMS fill:#f9f,stroke:#333,stroke-width:2px
style UserWhatsApp fill:#9cf,stroke:#333,stroke-width:2pxNote: If this Mermaid diagram doesn't render in your Markdown processor, generate a static image as a fallback and include appropriate alt text.
1. Setting up the project
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-node-messaging cd vonage-node-messaging -
Initialize Node.js Project: Initialize a new Node.js project using npm. The
-yflag accepts default settings.bashnpm init -yThis creates a
package.jsonfile. -
Install Dependencies: Install Express for the web server, the Vonage SDKs for API interaction, and
dotenvfor managing environment variables.bashnpm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenv -
Create Project Structure: Create a simple structure for your source code.
bashmkdir src touch src/server.js touch .env touch .gitignore -
Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing them to version control. Never commit sensitive credentials.Codenode_modules/ .env -
Set Up Environment Variables (
.env): Open the.envfile and add the following variables. You'll fill these in during the Vonage setup section.dotenv# .env # Vonage API Credentials (found on Vonage Dashboard homepage) VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Details (generated when creating a Vonage Application) VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_PRIVATE_KEY=./private.key # Path to your downloaded private key file (for local dev) # Vonage Numbers (your purchased Vonage number and the WhatsApp Sandbox number) VONAGE_SMS_FROM_NUMBER=YOUR_VONAGE_SMS_NUMBER # Verify the Sandbox number from your Vonage Dashboard (Messages API Sandbox page) VONAGE_WHATSAPP_SANDBOX_NUMBER=14157386102 # Webhook Security (found in Vonage Dashboard > Settings) VONAGE_SIGNATURE_SECRET=YOUR_SIGNATURE_SECRET # Server Configuration PORT=8000 # Port for the Express serverVONAGE_API_KEY,VONAGE_API_SECRET: Your main account credentials.VONAGE_APPLICATION_ID: Identifies your specific Vonage application configuration.VONAGE_PRIVATE_KEY: Path to the private key file used for authenticating requests related to your Vonage application (required for Messages API). For deployment, handle this differently (see Section 8).VONAGE_SMS_FROM_NUMBER: Your purchased Vonage number in E.164 format (e.g.,12015550123).VONAGE_WHATSAPP_SANDBOX_NUMBER: The number provided by Vonage for the WhatsApp Sandbox. Verify this on your dashboard.VONAGE_SIGNATURE_SECRET: Used to verify that incoming webhooks originated from Vonage.PORT: The local port your Express server listens on.
2. Vonage account and application setup
Configure your Vonage account and create a Vonage Application to handle messaging.
- Log in to Vonage Dashboard: Access your Vonage API Dashboard.
- Find API Key and Secret: On the dashboard homepage, locate your API key and API secret. Copy these values into the corresponding
VONAGE_API_KEYandVONAGE_API_SECRETvariables in your.envfile. - Create a Vonage Application:
- Navigate to Applications > Create a new application.
- Give your application a name (e.g., "Node Messaging App").
- Click Generate public and private key. Important: A
private.keyfile will download. Save this file in your project's root directory (same level as your.envfile). Ensure theVONAGE_PRIVATE_KEYpath in your.envfile points to this file (e.g.,./private.key). - Enable Messages under Capabilities. Configure the webhook URLs later after starting ngrok. Leave the Inbound URL and Status URL fields blank for now.
- Click Generate new application.
- On the next screen, copy the Application ID and paste it into the
VONAGE_APPLICATION_IDvariable in your.envfile.
- Link Your Vonage SMS Number:
- Go back to your Application's settings page.
- Scroll down to Link virtual numbers.
- Find the Vonage phone number you purchased earlier and click Link. This associates incoming SMS messages on this number with your application's webhooks.
- Copy this number in E.164 format (e.g.,
12015550123) into theVONAGE_SMS_FROM_NUMBERvariable in your.envfile.
- Find Signature Secret:
- Navigate to Settings in the main dashboard menu.
- Under API settings, find your Signature secret. Copy this value and paste it into the
VONAGE_SIGNATURE_SECRETvariable in your.envfile. - Important: Ensure Default SMS Setting is set to Use the Messages API. Save changes if needed.
- Set Up WhatsApp Sandbox:
- Navigate to Messages API Sandbox in the sidebar (under Build & Manage).
- You'll see QR codes and instructions to link your personal WhatsApp number to the sandbox. Scan the QR code with your phone's WhatsApp app or send the specified message to the Vonage Sandbox number (
+14157386102). - Follow the prompts in WhatsApp to allowlist your number for testing.
- Verify the
VONAGE_WHATSAPP_SANDBOX_NUMBERin your.envfile matches the number shown on the Sandbox page. - Add webhook URLs to the Sandbox configuration later.
3. Setting up the Express server
Now let's write the basic Express server code in src/server.js.
// src/server.js
require('dotenv').config(); // Load environment variables from .env file
const express = require('express');
const { Vonage } = require('@vonage/server-sdk');
const { WhatsAppText } = require('@vonage/messages');
const { verifySignature } = require('@vonage/jwt');
const fs = require('fs'); // Needed for reading private key file locally
// ---- Initialize Express App ----
const app = express();
// Middleware to parse JSON bodies & URL-encoded data
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// ---- Environment Variables Check ----
const requiredEnv = [
'VONAGE_API_KEY', 'VONAGE_API_SECRET', 'VONAGE_APPLICATION_ID',
'VONAGE_PRIVATE_KEY', 'VONAGE_SMS_FROM_NUMBER',
'VONAGE_WHATSAPP_SANDBOX_NUMBER', 'VONAGE_SIGNATURE_SECRET', 'PORT'
];
const missingEnv = requiredEnv.filter(key => !process.env[key]);
if (missingEnv.length > 0) {
console.error(`Error: Missing required environment variables: ${missingEnv.join(', ')}`);
console.error('Please check your .env file or environment configuration.');
process.exit(1); // Exit if critical variables are missing
}
const PORT = process.env.PORT || 8000;
const VONAGE_SIGNATURE_SECRET = process.env.VONAGE_SIGNATURE_SECRET;
// ---- Initialize Vonage Client ----
// Determine private key source (file path for local dev, env var for production)
let privateKeyValue;
if (process.env.VONAGE_PRIVATE_KEY_CONTENT) {
// Option 1: Read directly from an env var containing the key content
privateKeyValue = process.env.VONAGE_PRIVATE_KEY_CONTENT.replace(/\\n/g, '\n'); // Handle potential escaped newlines
} else if (fs.existsSync(process.env.VONAGE_PRIVATE_KEY)) {
// Option 2: Read from file path specified in .env (for local dev)
privateKeyValue = fs.readFileSync(process.env.VONAGE_PRIVATE_KEY);
} else {
console.error('Error: VONAGE_PRIVATE_KEY path is invalid or VONAGE_PRIVATE_KEY_CONTENT env var is not set.');
process.exit(1);
}
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY, // Optional for this setup, but good practice
apiSecret: process.env.VONAGE_API_SECRET, // Optional for this setup
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: privateKeyValue // Use the loaded key content
}, {
// Set apiHost for WhatsApp Sandbox testing
// Remove or change this for production WhatsApp
apiHost: 'https://messages-sandbox.nexmo.com'
});
console.log('Vonage client initialized.');
// ---- Webhook Security Middleware ----
const verifyVonageSignature = (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
console.warn('Webhook Error: Missing or invalid Authorization header.');
return res.status(401).send('Unauthorized: Missing or invalid token');
}
const token = authHeader.split(' ')[1];
// It's recommended to check `req.body` directly if possible,
// but Express middleware might have already parsed it.
// The SDK's verifySignature expects the raw body or the parsed object.
if (verifySignature(token, VONAGE_SIGNATURE_SECRET, req.body)) {
console.log('Webhook Signature Verified Successfully.');
next(); // Signature is valid, proceed to the route handler
} else {
console.warn('Webhook Error: Invalid signature.');
res.status(401).send('Unauthorized: Invalid signature');
}
} catch (error) {
console.error('Webhook Signature Verification Error:', error);
res.status(500).send('Internal Server Error during signature verification');
}
};
// ---- Define Routes (We will add these later) ----
// Placeholder for sending messages API endpoint
app.post('/api/send-message', (req, res) => {
res.status(501).send({ message: 'Send message endpoint not implemented yet.' });
});
// Placeholder for inbound message webhook
app.post('/webhooks/inbound', verifyVonageSignature, (req, res) => {
console.log('Received Inbound Webhook (Stub)');
res.status(200).send('OK'); // Always respond with 200 OK
});
// Placeholder for message status webhook
app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
console.log('Received Status Webhook (Stub)');
res.status(200).send('OK'); // Always respond with 200 OK
});
// ---- Basic Error Handler ----
app.use((err, req, res, next) => {
console.error("Unhandled Error:", err.stack || err);
res.status(500).send('Something broke!');
});
// ---- Start Server ----
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
console.log('Ensure ngrok is running and webhooks are configured correctly.');
});Explanation:
- Load Env Vars:
require('dotenv').config()loads variables from.env. - Initialize Express: Sets up the Express application and middleware for parsing request bodies.
- Env Var Check: Verifies that all necessary environment variables are present.
- Initialize Vonage Client: Creates a
Vonageinstance. It now attempts to read the private key content either directly from an environment variableVONAGE_PRIVATE_KEY_CONTENT(for deployment) or from the file path specified inVONAGE_PRIVATE_KEY(for local development). TheapiHostis set specifically for the WhatsApp Sandbox. Remove or change this for production WhatsApp. - Webhook Security Middleware (
verifyVonageSignature): Intercepts webhook requests, extracts the JWT token, verifies it usingverifySignature, and passes control or sends an error response. Crucially, webhooks must be secured. - Route Placeholders: Defines basic routes, applying
verifyVonageSignatureonly to webhook routes. - Basic Error Handler: A simple catch-all middleware.
- Start Server: Starts the Express server.
4. Sending messages
Let's implement the functions to send SMS and WhatsApp messages.
- Add Sending Functions: Add these functions inside
src/server.jsbefore the route definitions.
// src/server.js
// ... (Keep existing code above)
// ---- Message Sending Functions ----
/**
* Sends an SMS message using the Vonage Messages API.
* @param {string} toNumber - The recipient's phone number (E.164 format).
* @param {string} text - The message content.
* @returns {Promise<string>} - The message UUID on success.
* @throws {Error} - If sending fails.
*/
const sendSmsMessage = async (toNumber, text) => {
const fromNumber = process.env.VONAGE_SMS_FROM_NUMBER;
console.log(`Attempting to send SMS from ${fromNumber} to ${toNumber}`);
try {
const response = await vonage.messages.send({
message_type: "text",
text: text,
to: toNumber,
from: fromNumber,
channel: "sms"
});
// Assuming SDK maps message_uuid to messageUuid based on code comments/context
console.log(`SMS sent successfully to ${toNumber}. Message UUID: ${response.messageUuid}`);
return response.messageUuid;
} catch (error) {
console.error(`Error sending SMS to ${toNumber}:`, error.response ? error.response.data : error.message);
// Rethrow or handle specific errors (e.g., invalid number, insufficient funds)
throw new Error(`Failed to send SMS: ${error.message}`);
}
};
/**
* Sends a WhatsApp message using the Vonage Messages API (Sandbox).
* @param {string} toNumber - The recipient's WhatsApp number (E.164 format).
* @param {string} text - The message content.
* @returns {Promise<string>} - The message UUID on success.
* @throws {Error} - If sending fails.
*/
const sendWhatsAppMessage = async (toNumber, text) => {
// For Sandbox, 'from' is the Sandbox number. For production, it's your provisioned WhatsApp number.
const fromNumber = process.env.VONAGE_WHATSAPP_SANDBOX_NUMBER;
console.log(`Attempting to send WhatsApp message from ${fromNumber} to ${toNumber}`);
try {
// Use the WhatsAppText message constructor for WhatsApp
const response = await vonage.messages.send(
new WhatsAppText({
text: text,
to: toNumber,
from: fromNumber, // Using Sandbox number here
})
);
// Assuming SDK maps message_uuid to messageUuid based on code comments/context
console.log(`WhatsApp message sent successfully to ${toNumber}. Message UUID: ${response.messageUuid}`);
return response.messageUuid;
} catch (error) {
console.error(`Error sending WhatsApp message to ${toNumber}:`, error.response ? error.response.data : error.message);
// Handle specific WhatsApp errors (e.g., user not allowlisted in sandbox, template required for production)
throw new Error(`Failed to send WhatsApp message: ${error.message}`);
}
};
// ---- Webhook Security Middleware (Keep existing code) ----
// const verifyVonageSignature = ...
// ---- Define Routes ----
// Update the /api/send-message endpoint
app.post('/api/send-message', async (req, res) => {
const { to, type, text } = req.body;
// Basic Input Validation
if (!to || !type || !text) {
return res.status(400).send({ error: 'Missing required fields: to, type, text' });
}
if (type !== 'sms' && type !== 'whatsapp') {
return res.status(400).send({ error: 'Invalid type. Must be "sms" or "whatsapp"' });
}
// Add more robust phone number validation if needed (e.g., using a library)
try {
let messageUuid;
if (type === 'sms') {
messageUuid = await sendSmsMessage(to, text);
} else { // type === 'whatsapp'
// Ensure the target number is allowlisted in the Sandbox for testing
console.warn(`Sending WhatsApp to ${to}. Ensure this number is allowlisted in the Sandbox.`);
messageUuid = await sendWhatsAppMessage(to, text);
}
res.status(200).send({ message: `${type.toUpperCase()} sent successfully`, messageUuid });
} catch (error) {
console.error(`Failed to send message via API endpoint: ${error.message}`);
// Provide a more user-friendly error message
res.status(500).send({ error: `Failed to send ${type.toUpperCase()} message.`, details: error.message });
}
});
// ---- Webhook Routes (Keep existing code) ----
// app.post('/webhooks/inbound', verifyVonageSignature, ...
// app.post('/webhooks/status', verifyVonageSignature, ...
// ---- Basic Error Handler (Keep existing code) ----
// app.use((err, req, res, next) => ...
// ---- Start Server (Keep existing code) ----
// app.listen(PORT, () => ...Explanation:
sendSmsMessage: Sends SMS usingvonage.messages.sendwithchannel: "sms".sendWhatsAppMessage: Sends WhatsApp using theWhatsAppTextconstructor and the Sandbox number.- API Endpoint (
/api/send-message): HandlesPOSTrequests to send messages, validates input, and calls the appropriate sending function.
5. Receiving messages (webhooks)
To receive messages, we need to expose our local server to the internet using ngrok and configure Vonage to send webhooks to that public URL.
-
Start ngrok: Open a new terminal window and start ngrok, pointing it to the port your Express server is listening on (e.g., 8000).
bashngrok http 8000Copy the
https://Forwarding URL (e.g.,https://<unique-subdomain>.ngrok-free.app). This is your public webhook base URL. -
Configure Vonage Webhooks:
- Application Webhooks (for SMS):
- Go back to your Vonage Application settings in the dashboard.
- Under Messages capabilities, paste your ngrok Forwarding URL into the Inbound URL field, appending
/webhooks/inbound. - Paste your ngrok Forwarding URL into the Status URL field, appending
/webhooks/status. - Click Save changes.
- WhatsApp Sandbox Webhooks:
- Navigate to the Messages API Sandbox page in the dashboard.
- Click on the Webhooks tab.
- Paste your ngrok URL +
/webhooks/inboundinto the Inbound URL field. - Paste your ngrok URL +
/webhooks/statusinto the Status URL field. - Click Save webhooks.
- Application Webhooks (for SMS):
-
Implement Webhook Handlers: Update the placeholder webhook routes in
src/server.jsto actually process the incoming data.
// src/server.js
// ... (Keep existing code for initialization, sending functions, middleware)
// ---- Define Routes ----
// app.post('/api/send-message', ... (Keep existing code)
// ---- Webhook Routes ----
// Inbound Message Webhook
app.post('/webhooks/inbound', verifyVonageSignature, (req, res) => {
console.log('--- INBOUND WEBHOOK RECEIVED ---');
console.log('Timestamp:', new Date().toISOString());
// Log the full payload as structured JSON
console.log('Request Body:', JSON.stringify(req.body, null, 2));
const { channel, message_type, from, to, text, message_uuid } = req.body;
// Process based on channel
if (channel === 'sms') {
console.log(`Received SMS from ${from} to ${to}`);
if (message_type === 'text' && text) {
console.log(`Message Text: "${text}"`);
// --- Add your SMS processing logic here ---
// Example: Look up user, trigger reply, store message
// await processIncomingSms(from, text);
}
} else if (channel === 'whatsapp') {
console.log(`Received WhatsApp message from ${from} to ${to}`);
if (message_type === 'text' && text) {
console.log(`Message Text: "${text}"`);
// --- Add your WhatsApp processing logic here ---
// Example: Handle commands, interact with chatbot logic
// await processIncomingWhatsApp(from, text);
}
// Handle other WhatsApp message types (image, audio, location, etc.) if needed
// else { console.log(`Received WhatsApp message of type: ${message_type}`); }
} else {
console.log(`Received message on unhandled channel: ${channel}`);
}
// CRITICAL: Always respond with 200 OK quickly to acknowledge receipt.
res.status(200).send('OK');
});
// Message Status Webhook
app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
console.log('--- STATUS WEBHOOK RECEIVED ---');
console.log('Timestamp:', new Date().toISOString());
// Log the full payload as structured JSON
console.log('Request Body:', JSON.stringify(req.body, null, 2));
const { message_uuid, status, timestamp, error, usage } = req.body;
console.log(`Status update for Message UUID: ${message_uuid}`);
console.log(`Status: ${status} at ${timestamp}`);
if (error) {
console.error(`Error Code: ${error.code}, Reason: ${error.reason}`);
// --- Add error handling logic here ---
// Example: Log to error tracking service, notify admin, attempt retry if applicable
// await handleFailedMessage(message_uuid, error);
}
if (usage) {
console.log(`Message Cost: ${usage.price} ${usage.currency}`);
// --- Add usage tracking logic here ---
}
// Potential statuses: submitted, delivered, rejected, undeliverable, read (WhatsApp)
switch (status) {
case 'delivered':
console.log('Message successfully delivered.');
break;
case 'read':
console.log('WhatsApp message read by recipient.');
break;
case 'rejected':
case 'undeliverable':
console.warn(`Message failed with status: ${status}`);
// Consider adding specific handling for failed messages
break;
default:
console.log(`Received status: ${status}`);
}
// CRITICAL: Always respond with 200 OK quickly.
res.status(200).send('OK');
});
// ---- Basic Error Handler (Keep existing code) ----
// app.use((err, req, res, next) => ...
// ---- Start Server (Keep existing code) ----
// app.listen(PORT, () => ...Explanation:
- Inbound Webhook (
/webhooks/inbound): Logs the full incoming payload usingJSON.stringifyfor better structure. Differentiates processing based onchannel. Responds with200 OKpromptly. - Status Webhook (
/webhooks/status): Logs the status update payload usingJSON.stringify. Provides details on message delivery, errors, and usage. Responds with200 OKpromptly.
6. Error handling and logging refinement
While we have basic try...catch and logging, let's refine it slightly. For production, consider dedicated logging libraries (pino, winston) and error tracking services (Sentry, Rollbar).
- Structured Logging: Using
JSON.stringifyin the webhook handlers (as implemented above) is a step towards structured logging, making logs easier to parse. You can enhance this further by adopting a standard log format across your application.javascript// Example structured log (more detailed) console.log(JSON.stringify({ level: 'info', timestamp: new Date().toISOString(), message: 'Received inbound SMS', details: { from: from, to: to, messageId: message_uuid } })); // Example structured error log console.error(JSON.stringify({ level: 'error', timestamp: new Date().toISOString(), message: 'Failed to send SMS', error: { message: error.message, stack: error.stack }, // Include stack trace context: { recipient: toNumber, function: 'sendSmsMessage' } })); - Specific Error Handling: Catch specific Vonage API errors if possible by inspecting
error.response.dataorerror.messagefor details provided by Vonage. - Webhook Retries: Remember Vonage retries webhooks if they don't receive a
200 OK. Your webhook handler should be idempotent (safe to run multiple times with the same input) or store processedmessage_uuids to prevent duplicate actions. - Retry Mechanisms (Sending): For transient network errors when sending messages, consider implementing a simple retry strategy with exponential backoff (e.g., using libraries like
async-retry).
7. Verification and testing
Now, let's test the complete flow.
-
Ensure Services are Running:
- Your Node.js server (
node src/server.jsin one terminal). - ngrok (
ngrok http <PORT>in another terminal).
- Your Node.js server (
-
Test Sending SMS:
- Use
curlor Postman. Replace<YOUR_PERSONAL_PHONE_NUMBER>(E.164 format).
bashcurl -X POST http://localhost:8000/api/send-message \ -H "Content-Type: application/json" \ -d '{ "to": "<YOUR_PERSONAL_PHONE_NUMBER>", "type": "sms", "text": "Hello from Vonage Node App! (SMS)" }'- Check: SMS received, server logs success, status webhooks arrive.
- Use
-
Test Sending WhatsApp:
- Ensure your number is allowlisted in the Sandbox. Replace
<YOUR_PERSONAL_WHATSAPP_NUMBER>(E.164 format).
bashcurl -X POST http://localhost:8000/api/send-message \ -H "Content-Type: application/json" \ -d '{ "to": "<YOUR_PERSONAL_WHATSAPP_NUMBER>", "type": "whatsapp", "text": "Hello from Vonage Node App! (WhatsApp Sandbox)" }'- Check: WhatsApp message received, server logs success, status webhooks arrive.
- Ensure your number is allowlisted in the Sandbox. Replace
-
Test Receiving SMS:
- From your personal phone, send an SMS to your
VONAGE_SMS_FROM_NUMBER. - Check: ngrok shows
POST /webhooks/inbound, server logs signature verification, inbound webhook details (channel: sms), and message text.
- From your personal phone, send an SMS to your
-
Test Receiving WhatsApp:
- From your personal WhatsApp, send a message to the
VONAGE_WHATSAPP_SANDBOX_NUMBER(+14157386102). - Check: ngrok shows
POST /webhooks/inbound, server logs signature verification, inbound webhook details (channel: whatsapp), and message text.
- From your personal WhatsApp, send a message to the
8. Deployment and CI/CD considerations
Deploying this application requires replacing ngrok with a permanent public URL and managing environment variables securely.
- Choose a Hosting Platform: Options include Heroku, Vercel, AWS, Google Cloud, Azure, etc.
- Environment Variables: Configure your environment variables (
VONAGE_API_KEY,VONAGE_API_SECRET, etc.) directly on the hosting platform. Do not commit your.envfile.- Private Key Handling: Instead of using a file path for
VONAGE_PRIVATE_KEY, set the content of yourprivate.keyfile as an environment variable (e.g.,VONAGE_PRIVATE_KEY_CONTENT). Many platforms support multi-line environment variables. Ensure yoursrc/server.jsreads this variable, as shown in the updated initialization code in Section 3. You might need to handle newline characters (\n) correctly (e.g., replace literal\nwith actual newlines if your platform escapes them). - Example Code Modification (already done in Section 3): The
Vonageclient initialization insrc/server.jsnow checks forprocess.env.VONAGE_PRIVATE_KEY_CONTENTfirst before falling back to the file path.
- Private Key Handling: Instead of using a file path for
- Public URL: Your hosting platform will provide a public URL (e.g.,
https://your-app-name.herokuapp.com). - Update Vonage Webhooks: Replace the ngrok URLs in your Vonage Application and Messages API Sandbox settings with your permanent public URL (e.g.,
https://your-app-name.herokuapp.com/webhooks/inboundand/status). - Procfile (for Heroku/similar): You might need a
Procfile:Procfileweb: node src/server.js - CI/CD Pipeline: Set up a pipeline (GitHub Actions, GitLab CI, etc.) for automated testing, building, and deployment, managing environment variables securely.
9. Troubleshooting and caveats
- Invalid Credentials: Double-check API keys, secret, Application ID, and private key content/path.
- Webhook Failures (401 Unauthorized): Incorrect
VONAGE_SIGNATURE_SECRETor issues with theverifySignatureimplementation. - Webhook Failures (No Response / Timeouts): Server not responding
200 OKquickly, or the server crashed. Check server logs. Vonage expects a response within a few seconds. - WhatsApp Sandbox Limitations: Only works with allowlisted numbers. Message templates are required for production WhatsApp outside the 24-hour customer care window. The
apiHostneeds to be changed for production WhatsApp. - Rate Limits: Be aware of Vonage API rate limits. Implement appropriate error handling and backoff strategies.
- Number Formatting: Ensure all phone numbers are in E.164 format (e.g.,
+12015550123). - SMS Sender ID: SMS sender ID behavior varies by country. Your Vonage number might be overwritten in some regions.
- Idempotency: Ensure webhook handlers are idempotent to handle potential retries from Vonage safely.