Frequently Asked Questions
Use the Vonage Messages API and Node.js SDK. Install the @vonage/server-sdk
package, initialize a Vonage client with your API credentials, and use the vonage.messages.send()
method with a new SMS
object containing the recipient, sender, and message text. This allows you to send text messages programmatically.
A Vonage webhook is a mechanism for receiving real-time notifications from the Vonage APIs about events related to your application, such as delivery status updates for SMS messages or incoming messages sent to your Vonage number. Your application provides URLs where Vonage sends these updates as HTTP POST requests.
Webhooks provide reliable delivery confirmation. Sending an SMS through an API is often "fire and forget," meaning the initial response only confirms the message was accepted, not delivered. Webhooks give you real-time feedback as the message travels through the mobile network to the recipient's device.
Create a Vonage application in your dashboard, enable the Messages capability, and provide the URL of your webhook endpoint for the 'Status URL' field. This URL will receive POST requests from Vonage containing delivery updates. ngrok can be used during development to expose a local server URL.
The Vonage Messages API is a versatile API enabling you to send and receive messages across various channels, including SMS, MMS, WhatsApp, and more. This tutorial demonstrates its use specifically for sending and tracking the delivery status of SMS messages.
Set up an 'Inbound URL' for your Vonage application in the dashboard. When someone sends an SMS to your Vonage number, Vonage sends an HTTP POST request to this URL containing the message content. Your application can then process this inbound message data.
You will need Node.js and npm installed, a Vonage account (free tier is available), a rented Vonage virtual number, and ngrok installed and authenticated. The Vonage account gives you API credentials and test credits. ngrok is used to expose your local development server.
Ngrok creates a public URL that tunnels to your local development server. This is necessary because Vonage's webhooks need to reach your local machine, which isn't directly accessible from the internet. Ngrok provides the bridge between your local server and Vonage's webhooks.
Do not use ngrok or unprotected webhook endpoints in production. Secure your webhooks using either Signed Webhooks with a shared secret or JWT verification as described in the Vonage documentation. This prevents unauthorized parties from sending fake requests to your server.
The message_uuid
is a unique identifier for each message sent through the Vonage Messages API. It is included in both the initial API response when sending the message and in all subsequent status webhook updates. Use it to track the lifecycle of individual messages.
Vonage provides various status updates, including 'submitted' (accepted by Vonage), 'delivered' (confirmed by the carrier), 'rejected' (rejected by Vonage or the carrier), and 'undeliverable'. Consult the Vonage documentation for a complete list and explanation of these statuses.
The Vonage Node.js SDK (@vonage/server-sdk
) simplifies interaction with Vonage APIs within a Node.js environment. It handles the complexities of API requests and responses, making it easier to send SMS messages, receive webhooks, and work with other Vonage services.
Yes, emojis are generally supported. However, using emojis or non-GSM characters might cause the message to be encoded as Unicode, which can reduce the maximum character count per SMS part (segment). Vonage handles this, but it's something to be aware of.
The express.json()
middleware is crucial for parsing incoming JSON data. Vonage's webhook POST requests contain the payload (delivery status, inbound message data) as JSON in the request body. Without express.json()
, you won't be able to access this JSON data in your route handlers.
How Do You Track SMS Delivery Status with Node.js and Vonage Webhooks?
Build a Node.js application using Express to send SMS messages via the Vonage Messages API and receive real-time delivery status updates through webhooks. SMS delivery failures cost businesses an estimated 5–10% of messages due to network issues, invalid numbers, and carrier rejections. Webhooks provide the only reliable way to confirm message delivery and handle failures.
By the end of this tutorial, you'll have a functional application capable of:
This pattern powers critical use cases like two-factor authentication (2FA), appointment reminders, order notifications, and alert systems where delivery confirmation is essential.
Project Overview and Goals
What You'll Build:
Build a simple Node.js application consisting of two main parts:
index.js
) to send an outbound SMS message using the Vonage Messages APIserver.js
) to listen for incoming webhook events from Vonage, specifically:submitted
,delivered
,rejected
)Problem Solved:
Sending an SMS via an API is often a "fire and forget" operation. The initial API response only confirms that the message was accepted by the platform, not that it was delivered to the user's phone. Mobile carrier networks can introduce delays or failures. This guide addresses the need for reliable delivery confirmation by implementing status webhooks. It also provides the foundation for two-way communication by handling inbound messages.
Technologies Used:
@vonage/server-sdk
): Simplifies interaction with Vonage APIs within a Node.js environment.env
file intoprocess.env
System Architecture:
Vonage implements a webhook retry mechanism with exponential backoff. If your server doesn't respond with a 2xx status code within 5 seconds, Vonage retries the webhook up to 5 times over approximately 24 hours.
Prerequisites:
How Do You Set Up the Vonage SMS Project?
Create the project structure and install the necessary dependencies.
Create Project Directory: Open your terminal or command prompt and create a new directory for your project, then navigate into it.
Initialize Node.js Project: This creates a
package.json
file to manage dependencies and project metadata.Install Dependencies: Install the Vonage SDK (
@vonage/server-sdk
) for API interaction, Express for webhook handling, and dotenv for environment variable management.Create Project Files: Create the main files for sending SMS and running the webhook server.
Configure
.gitignore
: Prevent sensitive information and unnecessary files from being committed to version control. Add the following lines to your.gitignore
file:Project Structure: Your project directory should now look like this:
How Do You Integrate with Vonage?
Before writing code, configure Vonage to enable communication and provide necessary credentials.
Obtain API Key and Secret:
.env
file shortly.Create a Vonage Application: Applications act as containers for your communication settings, including webhook URLs and security credentials (like private keys) needed for certain APIs like Messages. The Application credentials (private key + Application ID) provide enhanced security and link your messages to specific webhook endpoints, while API Key/Secret provide basic account-level authentication.
Node SMS Status Guide App
).private.key
file that downloads. For simplicity in this guide, save it directly into your project's root directory (vonage-sms-status-guide/
), which matches the default path we'll use in.env
. The key file's location just needs to match theVONAGE_PRIVATE_KEY_PATH
variable in your.env
file. We've already addedprivate.key
to.gitignore
for security.ngrok
. For now, you can leave them blank or enter placeholder URLs likehttp://example.com/inbound
andhttp://example.com/status
.Link Your Vonage Number: Associate your purchased Vonage virtual number with the application you just created so Vonage knows where to route incoming messages and which webhooks to use for status updates related to that number.
Configure
.env
File: Open the.env
file and add your credentials. Replace the placeholder values with your actual keys, ID, number, and your test phone number. Ensure your Vonage number andTO_NUMBER
are in E.164 format (without the leading '+').Validate your environment variables before running the application. The Node.js script includes validation logic that checks for missing values and exits with an error message if any required variables are undefined or empty.
Security Note: The
.env
file should never be committed to public repositories. Ensure.env
is listed in your.gitignore
file.How Do You Expose Your Local Server with ngrok?
Vonage needs to send webhook events (status updates, inbound messages) to a publicly accessible URL. During development,
ngrok
creates a secure tunnel from the internet to your local machine. For production, replace ngrok with a permanent public URL from your hosting provider (Heroku, AWS, DigitalOcean, etc.).Start ngrok: Open a new terminal window/tab (keep your project terminal open). Run
ngrok
to forward traffic to the port your Express server will listen on (use port 3000).Copy the Forwarding URL:
ngrok
will display session information, including aForwarding
URL ending in.ngrok.io
or similar (use thehttps
version). It will look something likehttps://<random-subdomain>.ngrok.io
.Action: Copy this
https://...ngrok.io
URL. Keep thisngrok
terminal window running.Update Vonage Application Webhook URLs:
YOUR_NGROK_FORWARDING_URL/webhooks/status
(e.g.,https://<random-subdomain>.ngrok.io/webhooks/status
)YOUR_NGROK_FORWARDING_URL/webhooks/inbound
(e.g.,https://<random-subdomain>.ngrok.io/webhooks/inbound
)Purpose: This tells Vonage where to send HTTP POST requests for status updates and inbound messages related to the numbers linked to this application.
How Do You Send SMS Messages with Vonage?
Write the code to send an SMS message using the Vonage Node.js SDK.
Edit
index.js
: Add the following code to yourindex.js
file.Code Explanation:
require('dotenv').config();
: Loads variables from your.env
file intoprocess.env
.const fs = require('fs');
: Imports Node.js's built-in file system module, needed to read the private key file.require('@vonage/server-sdk')
: Imports the main Vonage SDK class.require('@vonage/messages')
: Imports specific message types (likeSMS
) for the Messages API.new Vonage(...)
: Initializes the Vonage client. For the Messages API v1 (used for sending SMS, WhatsApp, etc.), authentication primarily relies on theapplicationId
and the associatedprivateKey
content for enhanced security and linking capabilities like webhooks. TheprivateKey
option requires the actual key data, not the file path, hence the use offs.readFileSync()
. Debug mode ({ debug: true }
) provides verbose logging from the SDK, useful during development.sendSms
function:to
,from
, andtext
as arguments.vonage.messages.send()
which is the core method for the Messages API.new SMS(...)
object specifying the message details. The SDK handles structuring the request correctly for the API.async/await
for cleaner handling of the promise returned bysend()
.messageUuid
upon successful submission to Vonage. This ID is crucial for correlating status updates.sendSms
with values from.env
.How Do You Build the Webhook Server?
This Express server will listen for HTTP POST requests from Vonage at the URLs you configured (
/webhooks/status
and/webhooks/inbound
).Edit
server.js
: Add the following code to set up the Express server and webhook endpoints.Code Explanation:
require('express')
: Imports the Express framework.app = express()
: Creates an Express application instance.PORT
: Defines the port to listen on, defaulting to 3000.express.json()
andexpress.urlencoded()
: These are essential for parsing the incoming request bodies sent by Vonage. Vonage typically sends webhook data as JSON./webhooks/status
Route:POST
handler for the status URL.req.body
: Contains the parsed payload from Vonage.message_uuid
,status
,timestamp
,error
fields) and update your system accordingly (e.g., mark a message as delivered in a database).res.status(204).send()
: Crucial: Sends an HTTP204 No Content
response back to Vonage. Vonage requires a2xx
status code to confirm successful receipt. If it doesn't receive one within a timeout period, it will retry sending the webhook, potentially leading to duplicate processing./webhooks/inbound
Route:POST
handler for the inbound URL.params.from.number
), the message text (params.text
), etc.204 No Content
response./
Route: A simpleGET
route for checking if the server is running via a browser or health check tool.app.listen()
: Starts the server and makes it listen for connections on the specified port.How Do You Verify and Test the Implementation?
Run the application and verify that SMS sending and webhook handling work correctly.
Start the Webhook Server: In your primary terminal window (in the
vonage-sms-status-guide
directory), start the Express server.You should see output confirming the server is listening on port 3000.
Ensure ngrok is Running: Check the other terminal window where you started
ngrok
. It should still be running and showSession Status online
. If not, restart it (ngrok http 3000
) and re-update the webhook URLs in your Vonage application settings if the forwarding URL changed.Send a Test SMS: In a third terminal window/tab (or stop and restart the
server.js
process after sending if you only have two), run theindex.js
script to send the SMS.index.js
terminal):Attempting to send SMS...
Message sent successfully!
Message UUID: <a-long-unique-identifier>
SMS send request initiated.
Check Your Phone: You should receive the SMS message on the phone number specified in
TO_NUMBER
within a few seconds to a minute.Monitor the Webhook Server Console: Watch the terminal where
server.js
is running. As the message progresses through the network, Vonage will send POST requests to your/webhooks/status
endpoint via ngrok.server.js
terminal):--- Incoming Request ---
) for each webhook call.--- Status Webhook Received ---
followed by the JSON payload.status
values. Common statuses include:submitted
: The message has been accepted by Vonage and sent towards the carrier.delivered
: The carrier confirmed successful delivery to the handset. (This is the goal!)rejected
: Vonage or the carrier rejected the message (checkerror
field for reason).undeliverable
: The carrier could not deliver the message (e.g., invalid number, phone off for extended period).message_uuid
you saw logged byindex.js
.(Optional) Test Inbound SMS:
VONAGE_NUMBER
).server.js
console again.server.js
terminal):--- Incoming Request ---
--- Inbound Webhook Received ---
followed by the JSON payload containing the message details (sender number, text content, etc.).Verification Checklist:
server.js
starts without errors.ngrok
is running and forwarding to the correct port (3000)./webhooks/status
and/webhooks/inbound
.node index.js
logs aMessage UUID
and no errors.TO_NUMBER
).server.js
logs show incoming requests to/webhooks/status
.message_uuid
and progress through statuses (e.g.,submitted
->delivered
)./webhooks/inbound
inserver.js
.How Do You Handle Errors, Logging, and Retries?
Error Handling:
The
index.js
script includes basictry...catch
blocks to handle errors during SDK initialization or thesend
call. It logs detailed API errors if available. Theserver.js
includes a basic final error handling middleware, but production applications should have more specific error handling within routes.Common Vonage error codes include:
from
numberThe
error
object in status webhooks provides codes and reasons for delivery failures. Common delivery error codes include carrier rejections (1320), invalid destination numbers (1330), and content filtering violations (1340).Logging:
We use
console.log
andconsole.error
for basic logging. For production, consider using a dedicated logging library like Winston or Pino for structured logging, different log levels (debug, info, warn, error), and routing logs to files or external services. The Vonage SDK'sdebug: true
option provides verbose internal logging. Disable this in production.Webhook Retries:
Vonage automatically retries sending webhooks if your server doesn't respond with a
2xx
status code within 5 seconds. The retry schedule uses exponential backoff: immediately, 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours (approximately 5 retries over 24 hours). This is why quickly sendingres.status(204).send()
is vital.Your webhook handler should be idempotent – designed so that receiving the same webhook multiple times doesn't cause incorrect side effects (e.g., don't charge a user twice if you receive duplicate 'delivered' statuses). Check if you've already processed a specific
message_uuid
and status before taking action.What Are the Security Considerations?
.env
locally, system environment variables in production) and ensure.env
andprivate.key
are in.gitignore
.Find your signature secret in your Vonage Application settings under "Capabilities > Messages > Signed Webhooks". Follow the official Vonage Webhook Security documentation.
express-rate-limit
.How Do You Troubleshoot Common Issues?
Ngrok Issues:
Vonage Configuration Errors:
.env
.VONAGE_PRIVATE_KEY_PATH
in.env
points correctly to yourprivate.key
file and that the file exists and is readable.Webhook Not Receiving Data:
server.js
is running and listening on the correct port (3000).http://127.0.0.1:4040
by default) to see if requests are hitting ngrok but maybe not reaching your server.express.json()
middleware is correctly configured inserver.js
.Delivery Status Meanings:
Familiarize yourself with the possible status values and their meanings in the Vonage documentation.
Delivery Receipt (DLR) Support:
Not all mobile networks or countries provide reliable delivery receipts back to Vonage. The
delivered
status is best-effort based on carrier reporting.submitted
is generally very reliable. Countries with strong DLR support (>90%) include US, UK, Germany, France, and Australia. Countries with limited DLR support (<50%) include India, Brazil, and many African nations.Number Formatting:
Always use the E.164 format for phone numbers. While the strict format includes a leading
+
(e.g.,+14155552671
), Vonage APIs typically accept the format without the+
as well (e.g.,14155552671
). This guide uses the format without the+
in the.env
file and code examples for consistency.Character Encoding:
Standard SMS has limitations. For emojis or non-GSM characters, the message might be sent as Unicode, which reduces the character limit per SMS part. The Messages API generally handles this well.
How Do You Deploy to Production?
Deployment:
.env
file. EnsureVONAGE_PRIVATE_KEY_PATH
points to the correct location of your key file on the server, or consider storing the key content directly in an environment variable (ensure proper handling of newlines if doing this).process.env.PORT
).Next Steps:
message_uuid
,to
,from
,text
,timestamp
) and update their status based on webhook events. Use tables to track message state transitions (sent → submitted → delivered).vonage.messages.send()
.Frequently Asked Questions
What delivery statuses can I expect from Vonage SMS?
Vonage SMS delivery statuses include
submitted
(message accepted by Vonage),delivered
(confirmed delivery to recipient's device),rejected
(message rejected by carrier),failed
(delivery failed), andundeliverable
(recipient number invalid or unreachable). Thedelivered
status depends on carrier support for delivery receipts (DLRs), which varies by country and network. Thesubmitted
status is reliable and confirms Vonage accepted your message.How long does it take to receive delivery status webhooks?
Status webhooks arrive in real-time as the message progresses through the delivery pipeline. You typically receive the
submitted
status within 1–2 seconds after sending. Thedelivered
status arrives when the carrier confirms delivery, usually within seconds to minutes depending on network conditions. If you don't receive adelivered
status within 15–30 minutes, the carrier likely doesn't support delivery receipts for that destination.Why am I not receiving webhook callbacks on my local server?
Verify your ngrok tunnel is running and the forwarding URL hasn't changed (ngrok generates new URLs each time you restart). Check that your Vonage Application webhook URLs match your current ngrok URL exactly, including the
/webhooks/status
and/webhooks/inbound
paths. Ensure your Express server is running on port 3000. Use the ngrok web interface athttp://127.0.0.1:4040
to inspect incoming requests and debug connection issues.What's the difference between the submitted and delivered status?
The
submitted
status means Vonage successfully accepted your message and passed it to the carrier network. Thedelivered
status confirms the message reached the recipient's device. Think ofsubmitted
as "message sent to carrier" anddelivered
as "message received by user." Not all carriers provide delivery confirmation, so you may only receivesubmitted
for some destinations.How do I secure my webhook endpoints in production?
Never deploy webhook endpoints without security. Implement Vonage's webhook signature verification using JWT tokens. Each webhook request includes a JWT in the
Authorization
header signed with your signature secret. Use the@vonage/auth
package to verify the signature before processing webhook data:Additionally, implement HTTPS, rate limiting, and IP whitelisting for defense-in-depth security. Store your signature secret securely in environment variables.
Can I receive webhooks for messages sent through the Vonage Dashboard?
Yes, webhooks fire for all messages associated with your Vonage Application, regardless of how they were sent (API, Dashboard, or other tools). As long as the sending number is linked to your Application and webhook URLs are configured, you'll receive status updates and inbound messages. This allows you to track all messaging activity in one place.
What phone number format does Vonage require?
Vonage expects phone numbers in E.164 format: country code followed by subscriber number without spaces or special characters. While the formal E.164 specification includes a leading
+
(e.g.,+14155552671
), Vonage APIs accept numbers without it (e.g.,14155552671
). Always include the country code – US numbers start with1
, UK numbers with44
, etc. Use libraries likelibphonenumber-js
to validate and format numbers correctly.How do I handle delivery failures and retry logic?
Monitor the status webhook for
rejected
,failed
, orundeliverable
statuses. Implement exponential backoff retry logic for transient failures, waiting progressively longer between retry attempts:For permanent failures like invalid numbers, don't retry – log the failure and alert your system. Consider implementing a fallback channel (email, push notification) for critical messages that fail SMS delivery.
What's the maximum SMS message length I can send?
A standard SMS supports 160 characters using GSM-7 encoding. Messages with special characters, emojis, or Unicode use UCS-2 encoding, reducing the limit to 70 characters per segment. Longer messages are automatically split into multiple segments. The Vonage Messages API handles segmentation automatically, but each segment counts as a separate message for billing. Keep messages under 160 characters for GSM-7 or 70 characters for Unicode to avoid segmentation.
How do I test webhooks without exposing my local server?
Use ngrok to create a secure tunnel from the internet to your local development server. Run
ngrok http 3000
to expose port 3000 publicly. Ngrok provides an HTTPS URL that you configure in your Vonage Application. This allows Vonage to send webhooks to your local machine during development. The ngrok web interface athttp://127.0.0.1:4040
lets you inspect all webhook requests in real-time, making debugging easier.Can I track message delivery across multiple phone numbers?
Yes, link all your Vonage numbers to a single Vonage Application, and configure one set of webhook URLs for that Application. All messages from any linked number will trigger webhooks to your endpoints. Include the number information in your database when sending messages, then use the
message_uuid
from status webhooks to track which message and number the status update relates to. This centralized approach scales efficiently.Related Resources: