code examples
code examples
Vonage SMS Delivery Status and Callbacks with Node.js, Next.js, and Supabase
Learn how to implement SMS delivery status tracking and webhook callbacks using the Vonage Messages API with Node.js. This comprehensive guide covers sending SMS messages, receiving delivery receipts (DLR), and handling inbound message webhooks for real-time SMS communication tracking.
.gitignore
Learn how to implement SMS delivery status tracking and webhook callbacks using the Vonage Messages API with Node.js. This comprehensive guide covers sending SMS messages, receiving delivery receipts (DLR), and handling inbound message webhooks for real-time SMS communication tracking.
What You'll Build
By the end of this tutorial, you will have a production-ready Node.js application that:
- Sends SMS messages programmatically using the Vonage Messages API
- Receives real-time delivery status updates (submitted, delivered, failed, rejected)
- Handles inbound SMS messages sent to your Vonage virtual number
- Implements webhook endpoints for reliable two-way SMS communication
This implementation is essential for applications requiring SMS delivery confirmation, including:
- Two-factor authentication (2FA) systems that need delivery verification
- Notification platforms tracking message delivery success rates
- Customer support channels with real-time message receipt tracking
- Automated SMS campaigns monitoring delivery performance
Understanding Vonage SMS Delivery Status and Webhooks
When you send an SMS message through the Vonage Messages API, you receive an immediate HTTP response confirming the message was submitted to the carrier network. However, this doesn't guarantee the message reached the recipient's device. To track actual delivery, you need to implement delivery receipt (DLR) webhooks that receive real-time status updates as your message progresses through the carrier network.
This tutorial builds a robust Node.js backend that integrates Vonage's webhook system for complete SMS delivery tracking and two-way messaging capabilities.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express: A minimal and flexible Node.js web application framework used to create our webhook endpoints.
- Vonage Messages API: A unified API for sending and receiving messages across various channels (SMS, MMS, WhatsApp, etc.). We will focus on SMS.
@vonage/server-sdk: The official Vonage Node.js SDK for interacting with Vonage APIs.dotenv: A module to load environment variables from a.envfile for secure configuration management.ngrok: A tool to expose local development servers to the internet, necessary for testing Vonage webhooks.
System Architecture:
+-------------------+ +---------------------+ +-------------------+
| Your Application |----->| Vonage Messages API |----->| Carrier Network |-----> User's Phone
| (Node.js/Express)| | (Send SMS Request) | | |
+-------------------+ +---------------------+ +-------------------+
^ | | ^
| | (Webhook Callbacks) | | (Delivery Status / Inbound SMS)
| +------------------------+ |
| |
+-------------------+ +---------------------+
| ngrok Tunnel |<-----| Vonage Platform |
| (For Development) | | (Webhook Dispatch) |
+-------------------+ +---------------------+Prerequisites:
- A Vonage API account (Sign up here).
- Node.js (version 14 or higher) and npm installed on your machine (Download Node.js).
- A Vonage virtual phone number capable of sending/receiving SMS. You can get one from the Vonage Dashboard.
ngrokinstalled and authenticated (Download ngrok). A free account is sufficient.
Final Outcome:
You will have two main components:
- A script (
send-sms.js) to send an SMS message via Vonage and capture the unique message UUID for tracking. - An Express server (
server.js) with webhook endpoints for receiving delivery status updates and inbound messages from Vonage.
Setting Up Your Node.js Project for Vonage SMS Webhooks
Let's create the project structure and install the necessary dependencies for handling Vonage SMS delivery status callbacks.
1. Create Project Directory:
Open your terminal and create a new directory for your project, then navigate into it.
mkdir vonage-sms-callbacks
cd vonage-sms-callbacks2. Initialize Node.js Project:
Initialize a package.json file.
npm init -y(This accepts default settings. Feel free to omit -y to customize.)
3. Install Dependencies:
We need the Express framework, the Vonage SDK, and dotenv for managing environment variables.
npm install express @vonage/server-sdk dotenv --saveexpress: Web framework for handling webhook requests.@vonage/server-sdk: To interact with the Vonage APIs.dotenv: To load credentials securely from a.envfile.
4. Create Project Files:
Create the main files we'll be working with.
touch .env server.js send-sms.js .gitignore.env: Stores sensitive credentials (API keys, application ID, etc.).server.js: Runs the Express server to listen for webhooks.send-sms.js: Contains the logic to send an SMS message..gitignore: Specifies files/directories that Git should ignore (like.envandnode_modules).
5. Configure .gitignore:
Add the following lines to your .gitignore file to prevent committing sensitive information and unnecessary files:
node_modules
.env
private.key
*.log6. Set Up Environment Variables (.env):
Open the .env file and prepare it for your Vonage credentials. We'll fill these in later.
# .env - Fill these values in later
# Vonage API Credentials (Found in Vonage Dashboard -> API Settings)
VONAGE_API_KEY=YOUR_VONAGE_API_KEY
VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET
# Vonage Application ID (Generated in Step 2)
VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID
# Path to your Vonage Application private key file (Downloaded in Step 2)
VONAGE_PRIVATE_KEY_PATH=./private.key
# Your Vonage virtual number (e.g., 14155550100)
VONAGE_NUMBER=YOUR_VONAGE_NUMBER
# Recipient phone number for testing (e.g., 14155550101)
TO_NUMBER=RECIPIENT_PHONE_NUMBER
# Port for the local Express server
PORT=3000- Why
.env? Storing credentials directly in code is insecure. Using environment variables loaded from.env(which is gitignored) keeps secrets out of your source control.dotenvmakes this easy in development. In production, you'd typically set these variables directly in your hosting environment.
Configuring Vonage Webhooks for SMS Delivery Status
To receive delivery status callbacks and inbound messages, you must configure webhook URLs in your Vonage Application. Vonage will send HTTP POST requests to these endpoints whenever message events occur (delivery status changes, incoming messages).
1. Set Default SMS API to Messages API:
- Log in to the Vonage API Dashboard.
- Navigate to API Settings in the left-hand menu.
- Scroll down to the SMS settings section.
- Ensure the Default SMS Setting is set to Messages API. If not, select it and click Save changes.
- Why? Vonage has two SMS APIs (SMS API and Messages API). They use different webhook formats and configurations. This guide uses the newer Messages API, so setting it as the default ensures consistency.
2. Run ngrok:
Before creating the Vonage Application, you need a publicly accessible URL for your local server's webhooks. ngrok provides this. Open a new terminal window and run:
ngrok http 3000(Replace 3000 if you chose a different port in .env)
ngrok will display output similar to this:
Session Status online
Account Your Name (Plan: Free)
Version x.x.x
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding https://<random-string>.ngrok-free.app -> http://localhost:3000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00Copy the https://<random-string>.ngrok-free.app URL. This is your public base URL. Keep this terminal window running.
3. Create a Vonage Application:
- In the Vonage Dashboard, navigate to Applications > Create a new application.
- Enter an Application name (e.g.,
Node SMS Callbacks App). - Click Generate public and private key. This will automatically download a file named
private.key. Save this file in your project's root directory (the same place asserver.js). This key is used by the SDK to authenticate requests for this application. - Under Capabilities, find Messages and toggle it ON.
- Two fields will appear: Inbound URL and Status URL.
- Inbound URL: Paste your
ngrokForwarding URL and append/webhooks/inbound. Example:https://<random-string>.ngrok-free.app/webhooks/inbound - Status URL: Paste your
ngrokForwarding URL and append/webhooks/status. Example:https://<random-string>.ngrok-free.app/webhooks/status - Why these URLs? The Inbound URL is where Vonage sends data when your Vonage number receives an SMS. The Status URL is where Vonage sends delivery receipt updates for messages you send.
- Inbound URL: Paste your
- Leave other capabilities off for now.
- Click Generate new application.
- You'll be taken to the application's page. Copy the Application ID displayed near the top.
4. Link Your Vonage Number:
- On the application's page (or by going back to Applications and clicking your app name), scroll down to the Linked numbers section.
- Click Link next to the Vonage virtual number you want to use for sending and receiving SMS with this application.
- Confirm the linking.
5. Update .env File:
Now, open your .env file and fill in the values you obtained:
# .env - Update with your actual values
VONAGE_API_KEY=YOUR_VONAGE_API_KEY # From Dashboard API Settings
VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET # From Dashboard API Settings
VONAGE_APPLICATION_ID=YOUR_COPIED_APPLICATION_ID # From Step 3
VONAGE_PRIVATE_KEY_PATH=./private.key # Should be correct if saved in root
VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # e.g., 14155550100
TO_NUMBER=YOUR_TEST_RECIPIENT_PHONE_NUMBER # e.g., 14155550101
PORT=3000Ensure VONAGE_PRIVATE_KEY_PATH correctly points to where you saved the private.key file.
Sending SMS Messages with Node.js and Tracking Message UUIDs
Every SMS sent through the Vonage Messages API receives a unique message_uuid identifier. This UUID is critical for tracking delivery status updates, as Vonage includes it in webhook callbacks to match status updates with the original message.
File: send-sms.js
// send-sms.js
require('dotenv').config(); // Load environment variables from .env file
const fs = require('fs'); // Require Node.js file system module to read the private key
const { Vonage } = require('@vonage/server-sdk');
// Note: Messages capability is accessed via the main Vonage instance
// --- Configuration ---
const vonageApiKey = process.env.VONAGE_API_KEY;
const vonageApiSecret = process.env.VONAGE_API_SECRET;
const vonageApplicationId = process.env.VONAGE_APPLICATION_ID;
const vonagePrivateKeyPath = process.env.VONAGE_PRIVATE_KEY_PATH;
const vonageNumber = process.env.VONAGE_NUMBER;
const toNumber = process.env.TO_NUMBER;
// --- Input Validation (Basic) ---
if (!vonageApiKey || !vonageApiSecret || !vonageApplicationId || !vonagePrivateKeyPath || !vonageNumber || !toNumber) {
console.error('Error: Missing required environment variables. Check your .env file.');
process.exit(1); // Exit if configuration is incomplete
}
let privateKey;
try {
privateKey = fs.readFileSync(vonagePrivateKeyPath);
} catch (err) {
console.error(`Error reading private key file at path: ${vonagePrivateKeyPath}`);
console.error(err.message);
process.exit(1); // Exit if private key cannot be read
}
// --- Initialize Vonage Client ---
// Note: For Messages API with Application ID and Private Key,
// you initialize the main Vonage client slightly differently.
// The API Key and Secret are used for authentication along with the Application ID and Private Key.
const vonage = new Vonage({
apiKey: vonageApiKey,
apiSecret: vonageApiSecret, // Secret is still needed for some auth mechanisms, like signed webhooks
applicationId: vonageApplicationId,
privateKey: privateKey
});
// --- Define the send SMS function ---
async function sendSms() {
console.log(`Attempting to send SMS from ${vonageNumber} to ${toNumber}...`);
try {
// Use vonage.messages.send() for the Messages API
const resp = await vonage.messages.send({
message_type: "text",
text: "Hello from Vonage and Node.js!",
to: toNumber,
from: vonageNumber,
channel: "sms"
});
console.log('SMS Sent Successfully!');
console.log('Message UUID:', resp.messageUuid); // Save this UUID for tracking delivery status
} catch (err) {
console.error('Error sending SMS:');
if (err.response?.data) {
// Log detailed Vonage API error if available
console.error('Vonage API Error:', JSON.stringify(err.response.data, null, 2));
} else {
// Log the general error object
console.error(err);
}
}
}
// --- Execute the function ---
sendSms();Explanation:
- Load Env Vars & FS: Loads
.envvariables and the Node.jsfsmodule. - Configuration: Reads all necessary variables from
process.env. - Basic Validation: Checks if required environment variables are set.
- Read Private Key: Reads the private key file content using
fs.readFileSync. Includes error handling if the file is missing or unreadable. - Initialize Vonage Client: Creates a
Vonageinstance, passing the API key, secret, application ID, and the content of the private key. sendSmsFunction:- Defines an
asyncfunction to handle the asynchronous API call. - Uses
vonage.messages.send()which is the correct method for the Messages API. - Constructs the message payload specifying
message_type,text,to,from, andchannel. - Uses a
try...catchblock for error handling. - Logs the
messageUuidfrom the successful response (resp.messageUuid) - this UUID is essential for correlating delivery status updates. - Logs detailed error information if the API call fails.
- Defines an
- Execute: Calls the
sendSmsfunction to initiate the process.
Building Express Webhook Endpoints for SMS Delivery Status
Create Express.js endpoints to receive Vonage webhook callbacks. You'll need two separate endpoints: one for delivery status updates (/webhooks/status) and one for inbound messages (/webhooks/inbound).
File: server.js
// server.js
require('dotenv').config();
const express = require('express');
const app = express();
// Vonage needs to POST JSON data to the webhooks
app.use(express.json());
// Sometimes data might be URL-encoded (less common for Messages API webhooks)
app.use(express.urlencoded({ extended: true }));
const PORT = process.env.PORT || 3000; // Use port from .env or default to 3000
// --- Webhook Endpoints ---
// Endpoint for Inbound SMS Messages
app.post('/webhooks/inbound', (req, res) => {
console.log('--- Inbound SMS Received ---');
console.log('From:', req.body.from?.number || req.body.from); // Handle potential structure variations
console.log('To:', req.body.to?.number || req.body.to);
console.log('Message:', req.body.message?.content?.text || req.body.text); // Handle potential structure variations
console.log('Full Body:', JSON.stringify(req.body, null, 2));
console.log('--------------------------\n');
// Vonage requires a 200 OK response to acknowledge receipt of the webhook
res.status(200).end();
});
// Endpoint for Delivery Status Updates
app.post('/webhooks/status', (req, res) => {
console.log('--- Delivery Status Update ---');
console.log('Message UUID:', req.body.message_uuid);
console.log('Status:', req.body.status);
console.log('Timestamp:', req.body.timestamp);
if (req.body.error) {
console.error('Error Code:', req.body.error.code);
console.error('Error Reason:', req.body.error.reason);
}
console.log('Full Body:', JSON.stringify(req.body, null, 2));
console.log('--------------------------\n');
// Acknowledge receipt
res.status(200).end();
});
// --- Server Start ---
app.listen(PORT, () => {
console.log(`Server listening for webhooks at http://localhost:${PORT}`);
console.log('Ensure ngrok is running and configured in the Vonage Dashboard:');
console.log('Inbound URL: /webhooks/inbound');
console.log('Status URL: /webhooks/status');
});Explanation:
- Load Env Vars:
require('dotenv').config();loads the variables from.env. - Initialize Express:
const app = express();creates the Express application. - Middleware:
app.use(express.json());: Parses incoming requests with JSON payloads (which Vonage uses for webhooks).app.use(express.urlencoded({ extended: true }));: Parses incoming requests with URL-encoded payloads.
- Webhook Endpoints:
app.post('/webhooks/inbound', ...): Defines a handler for POST requests to the/webhooks/inboundpath. It logs relevant information from the request body (req.body).app.post('/webhooks/status', ...): Defines a handler for POST requests to the/webhooks/statuspath. It logs the delivery status information including themessage_uuidto correlate with sent messages.res.status(200).end();: This is critical. Vonage expects a200 OKresponse quickly to acknowledge receipt. Failure to send this promptly will cause Vonage to retry the webhook.
- Start Server:
app.listen(PORT, ...)starts the server on the specified port.
Implementing Proper Error Handling and Logging
The current code includes basic console.log statements. For production, you'd want more robust logging and error handling.
Error Handling (send-sms.js):
The try...catch block in the updated send-sms.js already handles errors during the API call more robustly. You can enhance this further:
// Example enhancement in send-sms.js catch block (already improved in Section 3)
// ... inside the catch block ...
console.error("Error sending SMS:");
// Log specific Vonage error details if available
if (err.response?.data) {
console.error("Vonage Error:", JSON.stringify(err.response.data, null, 2));
} else {
console.error(err); // Log the generic error
}
// Implement retry logic here if needed (e.g., for network errors)
// Send error notification (e.g., to Sentry, Datadog)
// ...Logging (server.js):
Replace console.log with a dedicated logging library like winston or pino for structured logging, different log levels (info, warn, error), and log rotation.
npm install winston --save// Example using Winston in server.js (simplified)
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console({ format: winston.format.simple() }),
// Add file transport for production
// new winston.transports.File({ filename: 'error.log', level: 'error' }),
// new winston.transports.File({ filename: 'combined.log' }),
],
});
// Replace console.log with logger.info, logger.warn, logger.error
// e.g., inside /webhooks/inbound
// logger.info('--- Inbound SMS Received ---');
// logger.info(`From: ${req.body.from?.number || req.body.from}`);
// ...etc.
// In case of webhook processing error:
// try { /* process webhook */ } catch (error) {
// logger.error('Error processing inbound webhook:', error);
// res.status(500).end(); // Respond with an error, but be cautious as Vonage might retry
// }
app.listen(PORT, () => {
logger.info(`Server listening for webhooks at http://localhost:${PORT}`);
// ...
});Retry Mechanisms (Vonage):
- Webhook Retries: Vonage automatically retries webhook delivery if it doesn't receive a
200 OK. Ensure your webhook endpoints respond quickly. Offload time-consuming processing. - Sending Retries: For sending SMS, implement your own retry logic within the
.catchblock insend-sms.jsif necessary (e.g., for transient network errors), potentially using exponential backoff.
Database Schema and Data Layer (Conceptual)
This guide focuses on the core integration. In a real application, you would store message details and status updates in a database.
Conceptual Schema (e.g., PostgreSQL):
CREATE TABLE sms_messages (
message_uuid UUID PRIMARY KEY, -- Vonage Message UUID
vonage_number VARCHAR(20) NOT NULL,
recipient_number VARCHAR(20) NOT NULL,
message_text TEXT,
direction VARCHAR(10) NOT NULL, -- 'outbound' or 'inbound'
status VARCHAR(20) DEFAULT 'submitted', -- e.g., submitted, delivered, failed, rejected
vonage_status_timestamp TIMESTAMPTZ, -- Timestamp from status webhook
error_code VARCHAR(10),
error_reason TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index for querying by status or recipient
CREATE INDEX idx_sms_messages_status ON sms_messages(status);
CREATE INDEX idx_sms_messages_recipient ON sms_messages(recipient_number);Data Layer Implementation:
- Use an ORM (like Prisma, Sequelize, TypeORM) or a query builder (like Knex.js).
- Sending: After
vonage.messages.send()succeeds, insert a record withdirection='outbound',status='submitted', and themessage_uuid. - Status Webhook: Find the message by
message_uuidand update itsstatus,vonage_status_timestamp, etc. - Inbound Webhook: Insert a new record with
direction='inbound',status='delivered', and details from the payload.
Adding Security Features
- Secure Credential Management: Handled via
.envand.gitignore. Use environment variables or secrets management in production. Never commit secrets. - Webhook Signature Verification (Recommended for Production): Vonage signs Messages API webhook requests using HMAC-SHA256 with your API Secret, allowing verification.
- Look for the
X-Vonage-Signatureheader in incoming webhook requests. - You'll need to generate the signature yourself using the request body and your
VONAGE_API_SECRETand compare it to the header value. - This prevents attackers from sending fake webhooks to your endpoints.
- Consult the Vonage Webhook Signature Documentation for the precise steps to implement signed webhook verification for the Messages API. The
@vonage/server-sdkmay offer utilities, but manual implementation might be needed. Implementing this is crucial for production security.
- Look for the
- Input Validation: Validate any user input if you build layers on top of this service.
- Rate Limiting: Apply rate limiting to public-facing endpoints, generally not needed for Vonage webhooks themselves.
- HTTPS:
ngrokprovides HTTPS. Ensure your production deployment uses HTTPS for webhook URLs.
Understanding SMS Delivery Status Values
Vonage delivery status webhooks include a status field indicating the current message state:
submitted: Message accepted by Vonage and sent to the carrier networkdelivered: Carrier confirmed the message reached the recipient's devicefailed: Delivery failed (network issues, invalid number, etc.)rejected: Message rejected by carrier (often due to content filtering)expired: Message expired before delivery (recipient phone off for extended period)
Important: Not all carriers support delivery receipts. In some countries/networks, you may only receive submitted status. Always handle cases where delivered status never arrives.
Additional Implementation Considerations
- Character Encoding: The Messages API handles Unicode characters and emojis correctly with
message_type: 'text' - Multipart Messages: Long SMS messages (over 160 characters) are automatically split into segments. You may receive separate delivery receipts for each segment
- Phone Number Formatting: Always use E.164 format (e.g.,
+14155550101) for international compatibility - Rate Limiting: Implement request queuing for bulk SMS sending to respect Vonage API rate limits
Implementing Performance Optimizations
- Fast Webhook Responses: Respond
200 OKimmediately. Offload slow processing (DB writes, external calls) to background jobs (e.g.,bullmq,agenda, AWS SQS). - Database Indexing: Index database tables (like
sms_messages) on frequently queried columns (message_uuid,status, numbers). - Resource Usage: Monitor Node.js memory/CPU. Use tools like
pm2for process management and clustering.
Adding Monitoring, Observability, and Analytics
- Health Checks: Add a
/healthendpoint returning200 OK. - Structured Logging: Use Winston/Pino with JSON format for log aggregation (Datadog, Splunk, ELK).
- Error Tracking: Integrate Sentry, Bugsnag, etc., to capture unhandled exceptions.
- Metrics: Track SMS sent/failed, inbound received, webhook response times, status counts. Use Prometheus/Grafana, Datadog APM.
- Dashboards: Visualize key metrics for system health and activity.
Troubleshooting Common Issues
- Incorrect Credentials/IDs: Double-check
VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_ID,VONAGE_PRIVATE_KEY_PATHin.env. Ensure the private key file exists and is readable. ngrokIssues:- Ensure
ngrokruns and points to the correct local port (3000). - Verify the
ngrokForwarding URL in the Vonage Application webhook settings is current (it changes on restart for free accounts). - Check the
ngrokweb interface (http://127.0.0.1:4040) for requests/errors.
- Ensure
- Firewall: Ensure your local firewall allows incoming connections on your server port (
3000). - Webhook Not Returning
200 OK: Check server logs (server.jsoutput) for errors if Vonage retries webhooks. Ensure quick responses. - Messages API Not Default: Re-verify Messages API is the default SMS setting in the Vonage Dashboard (API Settings) if encountering unexpected issues.
- Delivery Receipt (DLR) Limitations: DLRs aren't guaranteed by all carriers.
submittedconfirms handoff to the carrier. - Invalid
private.key: Ensure theprivate.keyfile matches theVONAGE_APPLICATION_IDand its content is correct. - Number Formatting Errors: Use E.164 format (
+1...) for phone numbers.
Deployment and CI/CD
- Beyond
ngrok: Deployserver.jsto a hosting provider (Heroku, AWS, Google Cloud, etc.). - Environment Variables: Configure production environment variables securely via your host. Do not deploy the
.envfile. Uploadprivate.keysecurely or store its content in an environment variable (handle newlines carefully). - Process Management: Use
pm2to run your Node.js app:
npm install pm2 -g # Install globally
pm2 start server.js --name vonage-webhooks- CI/CD Pipeline: Set up automated testing, building, and deployment (GitHub Actions, GitLab CI, Jenkins).
- Webhook URLs: Update Vonage Application webhook URLs to your production HTTPS endpoint.
- Rollback: Have a plan to revert deployments if issues arise.
Testing SMS Delivery Status Webhooks
1. Start Required Services:
- Run
ngrok http 3000and copy the Forwarding URL - Update your Vonage Application webhook URLs with the current
ngrokURL - Start the webhook server:
node server.js
2. Test Sending SMS:
- Open another terminal.
- Run:
node send-sms.js. - Expected:
- Script logs
SMS Sent Successfully! Message UUID: <uuid>. TO_NUMBERreceives the SMS.server.jslogs a "Delivery Status Update" (initiallysubmitted, then potentiallydelivered).
- Script logs
3. Test Receiving Inbound SMS:
- Send an SMS to your
VONAGE_NUMBER. - Expected:
server.jslogs an "Inbound SMS Received" entry.- Check
ngrokweb interface (http://127.0.0.1:4040) for request details.
4. Test Error Cases:
- Invalid Number: Change
TO_NUMBERto an invalid format insend-sms.js, run it, and observe script errors and potentialrejected/failedstatus updates. - Server Down: Stop
server.js, send an SMS viasend-sms.js. Observe webhook failures inngrok. Restartserver.jsto receive queued webhooks.
Verification Checklist:
- Dependencies installed (
npm install). .envpopulated correctly.private.keyfile present and correct path in.env.- Vonage Application created, Messages enabled.
- Webhook URLs point to correct
ngrok/production URL + paths. - Vonage number linked to Application.
ngrokrunning, forwarding correctly.server.jsrunning without errors.node send-sms.jssends SMS successfully.server.jslogs delivery status.- Sending SMS to
VONAGE_NUMBERlogs inbound message inserver.js. - Webhook endpoints return
200 OK.
Next Steps and Production Considerations
You now have a working implementation for tracking Vonage SMS delivery status with Node.js webhooks. To prepare for production:
- Implement webhook signature verification to validate requests are from Vonage (Vonage Webhooks Guide)
- Add database persistence to store message history and delivery status using PostgreSQL, MySQL, or MongoDB
- Set up structured logging with Winston or Pino for better observability
- Deploy to production hosting (Heroku, AWS, Google Cloud) with HTTPS endpoints
- Implement retry logic for failed message sends with exponential backoff
- Monitor delivery rates to identify carrier or configuration issues