Frequently Asked Questions
Use the Vonage Messages API with the @vonage/server-sdk in your Node.js application. The sendSms function demonstrates how to send text messages by specifying the recipient, sender, and message content. Remember to set up your API key, secret, application ID, private key path, and phone numbers correctly in your .env file.
The Vonage Messages API is a unified interface for sending various types of messages, including SMS. It offers robust features and multi-channel capabilities, making it suitable for applications needing reliable messaging and status tracking.
Webhooks provide real-time delivery status updates, including 'delivered,' 'failed,' or 'rejected,' directly to your application. This approach eliminates the need for constant polling and allows for immediate responses to message statuses, enabling better reliability and logging.
ngrok is essential during local development to expose your local server and receive webhooks from Vonage. For production deployments, you must use your server's public HTTPS URL configured in your Vonage application settings.
Yes, by setting up a webhook endpoint (e.g., /webhooks/status) in your Node.js application. Vonage will send real-time status updates to this endpoint, which you can then process to track deliveries, failures, and other events.
First, install the @vonage/server-sdk package. Then, configure your Vonage account, create an application, and obtain the necessary credentials (API Key, API Secret, Application ID, and Private Key). Finally, initialize the Vonage client in your Node.js code using these credentials.
The private key, along with the application ID, is crucial for authenticating your Node.js application with the Vonage Messages API and is the standard method when an application context is needed. This ensures secure communication and prevents unauthorized access to your account.
Responding with a 2xx status code (like 200 OK) is mandatory to acknowledge successful receipt of the Vonage webhook. Failure to respond correctly will cause Vonage to retry the webhook, leading to potential duplicate processing.
Design your webhook handler to be idempotent using the message_uuid, ensuring it can process the same status update multiple times without causing issues. If processing is lengthy, respond with 200 OK immediately and process asynchronously.
Check the error code and reason in the webhook payload. Common reasons include an invalid recipient number, the recipient blocking the number, carrier-specific restrictions, or insufficient funds in your Vonage account.
Store the message UUID, sender and recipient numbers, content, timestamps, status updates, and any error codes. Consider the provided conceptual database schema as a starting point.
Consult the Vonage Messages API documentation. They might use HMAC-SHA256 with your API secret or a dedicated webhook signing secret. Check if the Vonage SDK offers helper functions for signature verification.
Long SMS are split, but share a message UUID. Status updates may be per part or as a whole. Your logic should accommodate this, potentially grouping status updates by message UUID.
Implement webhook signature verification to prevent unauthorized requests. Use HTTPS, IP whitelisting if possible, and input sanitization to minimize security risks.
Vonage retries webhook delivery if your endpoint doesn't return a 2xx HTTP status code within a short time frame, indicating that the message hasn't been processed successfully.
Developer guide: Sending SMS and receiving delivery status callbacks with Node.js, Express, and Vonage
Build a Node.js application using Express to send SMS messages via the Vonage Messages API and receive delivery status webhooks with real-time tracking.
What are delivery status callbacks? When you send an SMS, you need to know whether it reached the recipient. Delivery status callbacks (also called Delivery Receipt Reports or DLRs) are HTTP POST requests Vonage sends to your server containing the message status –
delivered
,failed
,rejected
, or other states. Without these callbacks, you're sending messages blind with no confirmation of successful delivery.Why do you need them? Track delivery success rates, alert customers when critical messages fail, implement retry logic for failed sends, maintain audit logs for compliance, and provide users with real-time delivery feedback.
Project Overview and Goals
Build a Node.js application that demonstrates 2 core functionalities:
delivered
,failed
,rejected
) for the messages you send.Track delivery success for critical notifications like password resets, 2FA codes, appointment reminders, order confirmations, and payment alerts where knowing the delivery status directly impacts your application's reliability and user experience.
Technologies Used:
@vonage/server-sdk
: The official Vonage Node.js SDK (version 3.24.1 as of January 2025) for interacting with the API.dotenv
: A module to load environment variables from a.env
file for secure credential management.ngrok
: A tool to expose local development servers to the internet, necessary for testing webhooks during local development. Production deployments use your server's public URL.System Architecture:
(Note: For published documentation_ replace this ASCII diagram with an image for consistent rendering across platforms.)
Final Outcome:
By the end of this guide_ you'll have a functional Node.js application capable of:
/webhooks/status
).Expected time: 45–60 minutes for developers familiar with Node.js and Express. If you're new to webhooks or SMS APIs_ allow 90 minutes.
Prerequisites:
ngrok
: Install and authenticate. Download ngrok (A free account is sufficient).Pricing: Vonage charges per SMS sent (~$0.0070–$0.15 depending on destination country). New accounts receive free trial credit ($2.00 as of January 2025). Check current pricing at Vonage SMS Pricing.
1. Node.js Project Setup and Dependency Installation
Initialize the project_ install dependencies_ and set up the basic structure.
Create Project Directory: Open your terminal and create a new directory for your project_ then navigate into it.
Initialize Node.js Project: Initialize npm to create a
package.json
file.Install Dependencies: Install
express
for the web server_@vonage/server-sdk
to interact with the Vonage API_ anddotenv
to manage environment variables.Note: This installs
@vonage/server-sdk
version 3.24.1 (latest as of January 2025). This version uses TypeScript for improved code completion and follows modern async/await patterns.Create Project Structure: Create the necessary files and directories.
index.js
: Main file for the Express server (webhook listener).send-sms.js
: Script to trigger sending an SMS message..env
: Stores sensitive credentials (API keys_ etc.)..gitignore
: Specifies files/directories Git should ignore.Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing dependencies and sensitive credentials.Note: Add
private.key
assuming you might save the downloaded key file directly in the project root during development.Warning: While convenient for local development_ storing private keys directly in the project folder carries risks. Never commit your private key file to version control (Git). Ensure
.gitignore
correctly lists the key file name.Configure
.env
File: Open the.env
file and add placeholders for your Vonage credentials. Fill these in later.YOUR_API_KEY_HERE
_YOUR_API_SECRET_HERE
_YOUR_APPLICATION_ID_HERE
_ and the phone numbers with your actual values.14155552671
for a US number.2. Vonage Account Configuration and API Credentials
Configure your Vonage account_ create an application_ and obtain the necessary credentials.
Log in to Vonage Dashboard: Access your Vonage API Dashboard.
Find API Key and Secret: Your API Key and Secret appear prominently on the main dashboard page. Copy these values and paste them into your
.env
file forVONAGE_API_KEY
andVONAGE_API_SECRET
.Set Default SMS API: Ensure your account uses the Messages API for sending SMS_ as this guide relies on it.
Create a Vonage Application: The Messages API requires an application context for authentication using a private key.
SMS Status Guide App
).private.key
file. Save this file securely. For development_ place it in your project's root directory (as configured in.env
). Vonage typically names thisprivate.key
– ensure the path you set in.env
matches the exact filename and location where you saved it.ngrok
URL. For now_ leave them blank or enter temporary placeholders likehttp://example.com/status
..env
file forVONAGE_APPLICATION_ID
.Link Your Vonage Number: Link your Vonage virtual number to the application you just created.
.env
file forVONAGE_NUMBER
.Update
.env
with Private Key Path: EnsureVONAGE_PRIVATE_KEY_PATH
in your.env
file correctly points to where you saved the downloaded private key file (e.g.,./private.key
if it's in the root and namedprivate.key
). Match the actual filename if it differs.Your
.env
file should now contain your actual credentials (except for the webhook URLs, handled later).3. Implementing SMS Sending with Vonage Messages API
Write the script to send an SMS message using the Vonage Node.js SDK and the Messages API.
Edit
send-sms.js
: Open thesend-sms.js
file and add the following code:Code Explanation:
require('dotenv').config()
: Loads variables from the.env
file intoprocess.env
.process.env
. Includes basic validation.Auth
withapplicationId
andprivateKey
– this is the required authentication method for sending via the Messages API when an application context is needed (standard practice). While the API Key/Secret are included in theAuth
object, the Application ID and Private Key primarily drive authentication for this Messages API operation; the Key/Secret might be leveraged by other SDK functionalities or legacy API interactions.new Vonage(credentials, options)
creates the Vonage client instance.sendSms
Function:async
function handles the asynchronous API call.vonage.messages.send({...})
: The core method call.message_type: "text"
: Specifies a standard text message.text
: The SMS content. Maximum 160 characters for single-segment SMS using GSM-7 encoding. Longer messages split into multiple segments (up to 1,530 characters across 10 segments). Using Unicode characters reduces limit to 70 characters per segment.to
: The recipient's phone number (from.env
).from
: Your Vonage virtual number (from.env
).channel: "sms"
: Explicitly defines the channel.messageUuid
– a unique identifier (format:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
) that tracks this specific message through its lifecycle. Store this UUID to correlate webhook status updates with sent messages.try...catch
block to catch errors during the API call. Logs detailed error information if available in the Vonage response (err.response.data
).async sendSms
function immediately when you execute the script (node send-sms.js
).4. Building Express Webhook Endpoint for Delivery Status Callbacks
Create the Express server and the webhook endpoint (
/webhooks/status
) to receive delivery status updates from Vonage.Edit
index.js
: Openindex.js
and add the following code for the Express server:Code Explanation:
express.json()
before route definitions to ensure request bodies parse correctly before handlers execute.express.json()
: Parses incoming JSON request bodies (Vonage status webhooks use JSON).express.urlencoded()
: Parses URL-encoded bodies (less common for status webhooks but included for completeness)./webhooks/status
Endpoint (POST):req.body
: Contains the JSON payload from Vonage.message_uuid
,status
,timestamp
– crucial for debugging.if/else if
blocks demonstrating how you might react to different statuses (delivered
,failed
,rejected
). Integrate with your application's logic here (e.g., updating a database). TheIMPLEMENTATION POINT
comments highlight these areas.res.status(200).send('OK')
): Critically important. You must respond with a2xx
status code (like 200 or 204) quickly. If Vonage doesn't receive a timely2xx
response, it assumes delivery failed and retries sending the webhook, leading to duplicate processing./health
Endpoint (GET): A simple endpoint to check if the server is running.Complete webhook payload structure:
Timeout: Vonage waits 10 seconds for your webhook endpoint to respond with a
2xx
status code. If your endpoint doesn't respond within 10 seconds, Vonage retries the webhook. Implement retry logic as described in Section 6.5. Local Development with ngrok Tunneling for Webhook Testing
To receive webhooks from Vonage on your local machine, expose your local server to the internet using
ngrok
.Start Your Local Server: Open a terminal in your project directory and run:
You should see "Server listening on port 3000…" (or your configured port).
Start
ngrok
: Open a second terminal window (leave the first one running your server). Runngrok
to tunnel traffic to your local server's port (e.g., 3000).Get
ngrok
URL:ngrok
displays output similar to this:Copy the
https://<random-string>.ngrok-free.app
URL. This is your public webhook URL.Update Vonage Application Status URL:
SMS Status Guide App
).ngrok
URL into the Status URL field, appending/webhooks/status
. It should look like:https://<random-string>.ngrok-free.app/webhooks/status
https://<random-string>.ngrok-free.app/webhooks/inbound
if you plan to handle incoming SMS replies later (requires adding a/webhooks/inbound
route inindex.js
).Verification: Send a Test SMS:
Go back to the terminal where your
node index.js
server is not running.Run the send script again:
Note the
messageUuid
logged by the script.Observe Webhook:
node index.js
server is running.message_uuid
in the webhook payload matches the one logged bysend-sms.js
.status
field (e.g.,submitted
,delivered
,failed
). You might receive multiple updates for a single message as it progresses.Inspect with
ngrok
Web Interface: Open thengrok
Web Interface (usuallyhttp://127.0.0.1:4040
) in your browser to see incoming requests to your tunneled endpoint in real-time, inspect headers, and replay requests for debugging.Troubleshooting ngrok connection issues:
ngrok version
to verify.node index.js
in a separate terminal.6. Production-Ready Error Handling and Retry Logic
While basic logging is in place, enhance it for production:
Implement error handling middleware:
Robust Logging: Replace
console.log
with a structured logging library like Winston or Pino. Log levels (info, warn, error), timestamps, and request IDs make debugging easier. Log status updates to a persistent store or logging service.Webhook Retries: Vonage retries webhook delivery using this policy:
2xx
Ensure your endpoint responds quickly. If your processing logic is slow, acknowledge the request immediately (
res.status(200).send()
) and perform the processing asynchronously (e.g., using a job queue like BullMQ or Redis Queue).Idempotency: Design your webhook handler to be idempotent. If Vonage retries a webhook (e.g., due to a temporary network issue), your handler might receive the same status update multiple times. Use the
message_uuid
and potentially thetimestamp
or a unique webhook attempt ID (if provided by Vonage) to ensure you don't process the same update twice. Store processedmessage_uuid
+status
combinations temporarily if needed.Example idempotency check:
7. Webhook Security: JWT Signature Verification and Best Practices
Securing your webhook endpoint is crucial:
Webhook Signature Verification (Highly Recommended): Vonage uses JWT (JSON Web Token) Bearer Authorization with HMAC-SHA256 to sign webhook requests, allowing you to verify they originated from Vonage.
How Vonage Webhook Security Works:
Authorization: Bearer <token>
headerpayload_hash
claim (SHA-256 hash of the webhook body). Hash the incomingreq.body
and compare to this claim to detect tamperingiat
(issued at) claim is a UTC Unix timestamp. Compare to current time to reject stale/replayed tokensImplementation Example:
Note: Install
jsonwebtoken
package:npm install jsonwebtoken
HTTPS:
ngrok
provides HTTPS URLs, and your production deployment must use HTTPS to protect data in transit.IP Allowlisting: As an additional layer (or alternative if signature verification isn't feasible), configure your firewall or load balancer to only allow requests to your webhook endpoint from Vonage's known IP address ranges. Find current IP ranges at Vonage IP Ranges Documentation.
Input Sanitization: Although the data comes from Vonage, sanitize any data from the webhook before using it in database queries or other sensitive operations to prevent potential injection attacks if the data format unexpectedly changes.
Rate Limiting: Implement rate limiting on your webhook endpoint (e.g., using
express-rate-limit
) to prevent potential abuse or denial-of-service if your endpoint is exposed accidentally.Rate limiting example:
8. Database Schema Design for SMS Message Tracking
For production use, store SMS details and status updates.
Why Store Data? Track message history, auditing, analytics, provide status feedback to users, handle retries based on failure codes.
Example Schema (Conceptual):
Performance considerations:
recipient_number
,last_status
,submitted_at
,message_uuid
JSONB
forraw_payload
to enable efficient querying of webhook dataVARCHAR
lengths to optimize storageData retention: Implement a cleanup strategy to archive or delete old records:
Archive messages older than 90 days to separate cold storage
Delete status updates for archived messages after 1 year
Use scheduled jobs (cron or database triggers) for automated cleanup
Implementation:
send-sms.js
): Insert a new record intosms_messages
with themessage_uuid
and initial details.index.js
):sms_messages
usingmessage_uuid
.sms_status_updates
.last_status
,last_status_timestamp
, and potentiallyfinal_status
,error_code
,error_reason
in thesms_messages
table.9. Handling Special SMS Delivery Cases and Edge Scenarios
submitted
,delivered
,failed
,expired
,rejected
,accepted
,unknown
, etc.). Handlefailed
andrejected
specifically, potentially logging theerror.code
anderror.reason
provided in the webhook. Consult the official Vonage documentation for detailed explanations of all status and error codes.message_uuid
. Your status updates might reflect individual parts or the message as a whole depending on the carrier and Vonage processing.timestamp
field) are typically in UTC. Store them appropriately (e.g.,TIMESTAMPTZ
in PostgreSQL) and convert to local timezones only for display purposes.unknown
status means the carrier didn't provide delivery confirmation (common in certain regions). Treat as uncertain rather than failed. Don't automatically retry, but log for monitoring. If many messages showunknown
, consider alternative carriers or channels for that destination.10. Application Monitoring and Performance Metrics
/health
endpoint is a basic start. Production systems often require more detailed checks (e.g., database connectivity).prom-client
to expose metrics for Prometheus/Grafana.Metrics implementation example:
11. Vonage Delivery Status Codes and Error Reference
Understanding Vonage's Delivery Receipt (DLR) status codes is essential for proper error handling and monitoring.
Complete DLR Status Codes Reference
submitted
delivered
(DELIVRD)failed
(FAILED)rejected
(REJECTD)expired
(EXPIRED)undelivered
(UNDELIV)accepted
(ACCEPTD)unknown
(UNKNOWN)deleted
(DELETED)Source: Vonage DLR Statuses Documentation (accessed January 2025)
SMS Error Codes and Troubleshooting Guide
When status is
failed
orrejected
, the webhook includes anerror
object withcode
andreason
fields:Important: Vonage charges for messages regardless of final delivery status, except when rejected by the Vonage platform before submission (certain error codes like 2, 3, 4).
Source: Vonage SMS Delivery Error Codes (accessed January 2025)
12. Common Issues and Debugging Solutions
ngrok
Issues:ngrok
is running and the correct URL (HTTPS) is configured in the Vonage dashboard Status URL.ngrok
Account Limitations (2025):ngrok
accounts are sufficient for development but consider paid plans for production-like testing or higher bandwidth needs.ngrok
..env
values. Ensure the private key file path (VONAGE_PRIVATE_KEY_PATH
) is correct, matches the actual filename, and the file is readable by your application.https://…/webhooks/status
) in the Vonage application settings.node index.js
output) for errors during webhook processing.node send-sms.js
output) for API errors during sending.dotenv
is loading variables correctly (console.log(process.env.VONAGE_API_KEY)
early in the script to test).send-sms.js
log formessageUuid
).ngrok
is running and accessible.failed
/rejected
status):error.code
anderror.reason
in the webhook payload.vonage.messages.send
only means Vonage accepted the message for delivery, not that it was delivered. The webhook provides the actual delivery status.Manual webhook testing with curl:
13. Production Deployment and CI/CD Pipeline Setup
.env
variables securely using your hosting provider's mechanism (e.g., Heroku Config Vars, AWS Secrets Manager, Docker secrets). Never commit.env
files or private keys to Git.web: node index.js
), push code, set environment variables via the dashboard/CLI.Dockerfile
to containerize the application. Manage theprivate.key
securely (e.g., mount as a volume or use Docker secrets).ngrok
URL in the Vonage Application Status URL settings. Ensure it's HTTPS.VONAGE_PRIVATE_KEY_PATH
environment variable. Do not store it in version control.Example Dockerfile:
Example GitHub Actions workflow:
Frequently Asked Questions About Vonage SMS Delivery Status
How do I track SMS delivery status with Vonage?
Configure a webhook endpoint in your Vonage Application settings to receive delivery status callbacks. Vonage sends POST requests with status updates (
submitted
,delivered
,failed
,rejected
) to your webhook URL containing themessage_uuid
and status information.What is the difference between message submitted and delivered status?
submitted
means Vonage accepted your message and sent it to the carrier network.delivered
confirms the carrier successfully delivered the message to the recipient's device. Always wait fordelivered
status to confirm actual delivery.How long does it take to receive delivery status webhooks?
Delivery status webhooks typically arrive within seconds to minutes after sending. However, carrier delays can extend this to several minutes or hours. Some carriers in certain countries may not provide delivery receipts at all.
Why am I not receiving webhooks from Vonage?
Common causes: incorrect webhook URL in Vonage dashboard,
ngrok
not running, webhook endpoint not responding with2xx
status code, firewall blocking requests, or your SMS was never successfully sent (check send script logs for errors).How do I verify webhook requests are from Vonage?
Implement JWT signature verification using the signature secret from your Vonage Application settings. Verify the JWT token in the
Authorization
header, validate thepayload_hash
claim matches your request body hash, and check theiat
timestamp is recent (within 5 minutes).What HTTP status code should my webhook return?
Always return a
2xx
status code (typically200 OK
or204 No Content
) immediately to acknowledge receipt. If you don't respond with2xx
, Vonage assumes delivery failed and retries the webhook, causing duplicate processing.Can I test webhooks locally without deploying?
Yes, use
ngrok
to create a public HTTPS tunnel to your local development server. Configure thengrok
URL in your Vonage Application Status URL setting to receive webhooks on your local machine during development.What does error code 10 mean in delivery status?
Error code 10 ("Illegal sender") means the sender ID or phone number is not allowed for the destination country. Some countries require pre-registered sender IDs or only allow messages from local numbers.
How do I handle duplicate webhook deliveries?
Design your webhook handler to be idempotent. Store processed
message_uuid
+status
combinations and check before processing. Vonage retries webhooks if it doesn't receive a2xx
response, so duplicates can occur during network issues.Does Vonage charge for failed messages?
Yes, Vonage charges for messages regardless of final delivery status, except when rejected by the Vonage platform before submission (error codes 2, 3, 4). Carrier rejections and delivery failures still incur charges.