code examples

Sent logo
Sent TeamMar 8, 2026 / code examples / vonage

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:

  1. Sends SMS messages programmatically using the Vonage Messages API
  2. Receives real-time delivery status updates (submitted, delivered, failed, rejected)
  3. Handles inbound SMS messages sent to your Vonage virtual number
  4. 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 .env file for secure configuration management.
  • ngrok: A tool to expose local development servers to the internet, necessary for testing Vonage webhooks.

System Architecture:

text
+-------------------+      +---------------------+      +-------------------+
| 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.
  • ngrok installed and authenticated (Download ngrok). A free account is sufficient.

Final Outcome:

You will have two main components:

  1. A script (send-sms.js) to send an SMS message via Vonage and capture the unique message UUID for tracking.
  2. 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.

bash
mkdir vonage-sms-callbacks
cd vonage-sms-callbacks

2. Initialize Node.js Project:

Initialize a package.json file.

bash
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.

bash
npm install express @vonage/server-sdk dotenv --save
  • express: Web framework for handling webhook requests.
  • @vonage/server-sdk: To interact with the Vonage APIs.
  • dotenv: To load credentials securely from a .env file.

4. Create Project Files:

Create the main files we'll be working with.

bash
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 .env and node_modules).

5. Configure .gitignore:

Add the following lines to your .gitignore file to prevent committing sensitive information and unnecessary files:

text
node_modules
.env
private.key
*.log

6. Set Up Environment Variables (.env):

Open the .env file and prepare it for your Vonage credentials. We'll fill these in later.

dotenv
# .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. dotenv makes 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:

bash
ngrok http 3000

(Replace 3000 if you chose a different port in .env)

ngrok will display output similar to this:

text
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.00

Copy 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 as server.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 ngrok Forwarding URL and append /webhooks/inbound. Example: https://<random-string>.ngrok-free.app/webhooks/inbound
    • Status URL: Paste your ngrok Forwarding 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.
  • 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:

dotenv
# .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=3000

Ensure 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

javascript
// 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:

  1. Load Env Vars & FS: Loads .env variables and the Node.js fs module.
  2. Configuration: Reads all necessary variables from process.env.
  3. Basic Validation: Checks if required environment variables are set.
  4. Read Private Key: Reads the private key file content using fs.readFileSync. Includes error handling if the file is missing or unreadable.
  5. Initialize Vonage Client: Creates a Vonage instance, passing the API key, secret, application ID, and the content of the private key.
  6. sendSms Function:
    • Defines an async function 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, and channel.
    • Uses a try...catch block for error handling.
    • Logs the messageUuid from the successful response (resp.messageUuid) - this UUID is essential for correlating delivery status updates.
    • Logs detailed error information if the API call fails.
  7. Execute: Calls the sendSms function 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

javascript
// 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:

  1. Load Env Vars: require('dotenv').config(); loads the variables from .env.
  2. Initialize Express: const app = express(); creates the Express application.
  3. 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.
  4. Webhook Endpoints:
    • app.post('/webhooks/inbound', ...): Defines a handler for POST requests to the /webhooks/inbound path. It logs relevant information from the request body (req.body).
    • app.post('/webhooks/status', ...): Defines a handler for POST requests to the /webhooks/status path. It logs the delivery status information including the message_uuid to correlate with sent messages.
    • res.status(200).end();: This is critical. Vonage expects a 200 OK response quickly to acknowledge receipt. Failure to send this promptly will cause Vonage to retry the webhook.
  5. 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:

javascript
// 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.

bash
npm install winston --save
javascript
// 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 .catch block in send-sms.js if 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):

sql
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 with direction='outbound', status='submitted', and the message_uuid.
  • Status Webhook: Find the message by message_uuid and update its status, 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 .env and .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-Signature header in incoming webhook requests.
    • You'll need to generate the signature yourself using the request body and your VONAGE_API_SECRET and 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-sdk may offer utilities, but manual implementation might be needed. Implementing this is crucial for production security.
  • 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: ngrok provides 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 network
  • delivered: Carrier confirmed the message reached the recipient's device
  • failed: 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 OK immediately. 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 pm2 for process management and clustering.

Adding Monitoring, Observability, and Analytics

  • Health Checks: Add a /health endpoint returning 200 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_PATH in .env. Ensure the private key file exists and is readable.
  • ngrok Issues:
    • Ensure ngrok runs and points to the correct local port (3000).
    • Verify the ngrok Forwarding URL in the Vonage Application webhook settings is current (it changes on restart for free accounts).
    • Check the ngrok web interface (http://127.0.0.1:4040) for requests/errors.
  • Firewall: Ensure your local firewall allows incoming connections on your server port (3000).
  • Webhook Not Returning 200 OK: Check server logs (server.js output) 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. submitted confirms handoff to the carrier.
  • Invalid private.key: Ensure the private.key file matches the VONAGE_APPLICATION_ID and its content is correct.
  • Number Formatting Errors: Use E.164 format (+1...) for phone numbers.

Deployment and CI/CD

  • Beyond ngrok: Deploy server.js to a hosting provider (Heroku, AWS, Google Cloud, etc.).
  • Environment Variables: Configure production environment variables securely via your host. Do not deploy the .env file. Upload private.key securely or store its content in an environment variable (handle newlines carefully).
  • Process Management: Use pm2 to run your Node.js app:
bash
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 3000 and copy the Forwarding URL
  • Update your Vonage Application webhook URLs with the current ngrok URL
  • 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_NUMBER receives the SMS.
    • server.js logs a "Delivery Status Update" (initially submitted, then potentially delivered).

3. Test Receiving Inbound SMS:

  • Send an SMS to your VONAGE_NUMBER.
  • Expected:
    • server.js logs an "Inbound SMS Received" entry.
    • Check ngrok web interface (http://127.0.0.1:4040) for request details.

4. Test Error Cases:

  • Invalid Number: Change TO_NUMBER to an invalid format in send-sms.js, run it, and observe script errors and potential rejected/failed status updates.
  • Server Down: Stop server.js, send an SMS via send-sms.js. Observe webhook failures in ngrok. Restart server.js to receive queued webhooks.

Verification Checklist:

  • Dependencies installed (npm install).
  • .env populated correctly.
  • private.key file 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.
  • ngrok running, forwarding correctly.
  • server.js running without errors.
  • node send-sms.js sends SMS successfully.
  • server.js logs delivery status.
  • Sending SMS to VONAGE_NUMBER logs inbound message in server.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:

  1. Implement webhook signature verification to validate requests are from Vonage (Vonage Webhooks Guide)
  2. Add database persistence to store message history and delivery status using PostgreSQL, MySQL, or MongoDB
  3. Set up structured logging with Winston or Pino for better observability
  4. Deploy to production hosting (Heroku, AWS, Google Cloud) with HTTPS endpoints
  5. Implement retry logic for failed message sends with exponential backoff
  6. Monitor delivery rates to identify carrier or configuration issues