Frequently Asked Questions
Use the Vonage Messages API with the @vonage/server-sdk in your Node.js app. After setting up a Vonage application and linking your virtual number, make a POST request to the /send-sms endpoint with recipient number and message text in the request body. The server-side code will use vonage.messages.send() to send the SMS through the Vonage API.
The Vonage Messages API allows sending SMS messages, receiving inbound SMS, and tracking message delivery statuses. It's a unified API supporting several messaging channels, but this tutorial uses it for SMS because of its reliability and broad reach. It handles both outbound messaging and inbound webhooks.
Express.js simplifies setting up the web server and handling API requests and routing for our SMS application. It's lightweight and commonly used with Node.js. We define routes for sending SMS and receiving webhooks from Vonage.
ngrok is essential during development to expose your local server's webhook endpoints to the internet so Vonage can reach them. Vonage requires publicly accessible URLs for webhooks. Once deployed to a live server, ngrok is no longer required.
Yes, by setting up a status webhook URL in your Vonage application. The app will receive real-time delivery receipts (DLRs) at this endpoint, including the message UUID and delivery status (e.g., 'delivered', 'failed'). This is handled by the /webhooks/status route in the Express app.
Configure an inbound webhook URL in your Vonage application settings. Vonage will send incoming messages to this URL, which corresponds to the /webhooks/inbound route in your app. This route will receive the sender's number and message text. You'll need an SMS-enabled Vonage number linked to your application.
The private.key file is used for authentication with the Vonage Messages API, typically through JSON Web Tokens (JWT). The key is generated with your Vonage application and paired with a public key. Store it securely and never commit it to version control.
In the Vonage dashboard, create a new application, enable the Messages capability, and specify your ngrok HTTPS URLs for the inbound and status webhooks. Generate public and private keys, saving the private key securely. Then, link your Vonage virtual number to the application.
You'll need Node.js and npm installed, a Vonage API account with an API key and secret, a rented Vonage virtual number, ngrok for local development, and optionally the Vonage CLI. The tutorial also recommends using dotenv for environment variables.
dotenv loads environment variables from a .env file, keeping sensitive credentials like API keys out of your source code. This enhances security and makes managing configurations easier. It’s best practice for handling API secrets.
After setting up your Vonage application and linking the webhooks to ngrok URLs, start your Node.js server. Send an SMS to your Vonage number to trigger the inbound webhook. Send an SMS from your app to a test number to check the status webhook, observing logs for confirmation.
The @vonage/server-sdk simplifies interaction with Vonage APIs within your Node.js code. It handles authentication and provides methods like vonage.messages.send() to easily send messages and manage other Vonage services.
The Vonage Messages API provides delivery receipts (DLRs) via webhooks. Set up a status URL in your Vonage application. Your app will receive updates at this endpoint with the message UUID and status, enabling real-time delivery tracking within your application.
Vonage SMS API with Node.js and Express: Complete Tutorial with Webhooks and Delivery Tracking
Learn how to implement Vonage SMS delivery status tracking and webhooks in Node.js with Express. This hands-on tutorial shows you how to send SMS messages, receive delivery receipts (DLRs), handle inbound SMS via webhooks, and track message status in real-time. Perfect for building appointment reminders, order notifications, and two-factor authentication systems that need reliable delivery confirmation.
What you'll build: SMS webhooks with delivery tracking
Build a Node.js service that can:
Problem Solved: This application enables real-time, two-way SMS communication with status tracking – crucial for appointment reminders, order confirmations, two-factor authentication (2FA), and customer support notifications where delivery confirmation is essential.
Technologies Used:
.envfile intoprocess.env, keeping sensitive credentials out of source code.System Architecture:
Prerequisites:
npm install -g @vonage/cli. Useful for managing applications and numbers.1. Set up your Node.js Express project for Vonage webhooks
Create the project structure, install dependencies, and configure the environment.
1. Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
2. Initialize Node.js Project: Initialize the project using npm, accepting the defaults.
This creates a
package.jsonfile.3. Install Dependencies: Install Express for the web server, the Vonage SDK for API interaction, and
dotenvfor environment variable management.express: Web framework for handling HTTP requests and routes.@vonage/server-sdk: Simplifies calls to the Vonage APIs.dotenv: Loads environment variables from a.envfile.4. Create Project Structure: Create the necessary files and folders.
server.js: Will contain our main Express application logic..env: Stores sensitive configuration like API keys (DO NOT commit this file)..gitignore: Specifies intentionally untracked files that Git should ignore (like.envandnode_modules).private.key: Will store the private key downloaded from your Vonage Application (DO NOT commit this file).5. Configure
.gitignore: Add the following lines to your.gitignorefile to prevent committing sensitive information and unnecessary files:6. Configure
.env: Open the.envfile and add placeholders for your Vonage credentials and application details. Fill these in later.Explanation of Configuration Choices:
.envFile: Using a.envfile anddotenvis a standard practice for managing configuration and secrets in Node.js applications, keeping sensitive data separate from the codebase..gitignore: Essential for preventing accidental exposure of API keys, private keys, and environment files in version control systems like Git.private.key: The Vonage Messages API often uses JWTs generated with a public/private key pair for authentication via Applications. Storing the key securely is paramount.2. Configure Vonage webhooks for delivery status callbacks
Configure your Vonage account and create a Vonage Application to handle Messages API interactions and webhooks before writing code.
1. Get API Key and Secret: Log in to your Vonage API Dashboard. Your API Key and Secret are displayed at the top. Copy these values into your
.envfile forVONAGE_API_KEYandVONAGE_API_SECRET.2. Set Default SMS API: Navigate to your Account Settings in the Vonage dashboard. Scroll down to "API settings" > "SMS settings". Ensure "Default SMS Setting" is set to Messages API. This ensures webhooks use the Messages API format. Click "Save changes".
3. Create a Vonage Application: Vonage Applications act as containers for your communication configurations, including webhook URLs and authentication keys.
private.keyfile. Save this file in the root directory of your project (the same location as yourserver.js). Ensure theVONAGE_PRIVATE_KEY_PATHin your.envfile correctly points to it (./private.key). The public key is automatically stored by Vonage.ngrokin the next step. For now, you can enter temporary placeholders likehttp://example.com/webhooks/inboundandhttp://example.com/webhooks/status. You will update these shortly. Ensure the method is set to POST..envfile forVONAGE_APPLICATION_ID.4. Link Your Vonage Number: Scroll down on the Application details page to the "Link virtual numbers" section. Find the Vonage virtual number you rented and click the "Link" button next to it. This directs incoming messages and status updates for this number to the webhooks defined in this application. Copy this number (in E.164 format, e.g.,
18885551212) into your.envfile forVONAGE_NUMBER.5. Start ngrok: Get the public URL for your webhooks. Open a new terminal window (keep the first one for running the server later). Navigate to your project directory (optional but good practice) and run ngrok, telling it to expose the port your Express server will run on (defined as
PORTin.env, default is 3000).ngrok will display output similar to this:
Copy the
https://<random-string>.ngrok.ioURL (use the HTTPS version). This is your public base URL.6. Update Webhook URLs in Vonage Application: Go back to your Vonage Application settings in the dashboard (https://dashboard.nexmo.com/applications, find your app, and click "Edit").
YOUR_NGROK_HTTPS_URL/webhooks/inbound(e.g.,https://<random-string>.ngrok.io/webhooks/inbound)YOUR_NGROK_HTTPS_URL/webhooks/status(e.g.,https://<random-string>.ngrok.io/webhooks/status)Now, Vonage knows where to send incoming messages and status updates.
3. Implement Express webhook endpoints for SMS delivery tracking
Write the Node.js code in
server.jsto initialize the server, configure Vonage, send SMS, and handle the webhooks.Code Explanation:
dotenvfirst to ensure environment variables are available. Importexpressand necessary components from@vonage/server-sdk.express.json()andexpress.urlencoded()are crucial for parsing incoming webhook request bodies, which Vonage sends as JSON.FileSystemCredentials, providing the Application ID and the path to theprivate.keyfile. This method is required for authenticating Messages API calls associated with an Application./send-smsEndpoint:POSTroute to trigger sending an SMS.toandtextfrom the JSON request body.vonage.messages.send()within anasyncfunction.message_type: 'text',channel: 'sms', along withto,from, andtext.try...catchfor error handling, logging errors and returning appropriate HTTP status codes (400 for bad input, 500 or Vonage's status for API errors).message_uuidon success, which is useful for tracking./webhooks/inboundEndpoint:POSTroute matching the Inbound URL configured in Vonage.req.body) received from Vonage. This contains details about the incoming SMS (sender number, message text, timestamp, etc.).200 OKresponse. Vonage expects this acknowledgment; otherwise, it will retry sending the webhook, leading to duplicate processing.TODO) for adding your application-specific logic (database saving, auto-replies)./webhooks/statusEndpoint:POSTroute matching the Status URL configured in Vonage.message_uuidof the original outbound message and its deliverystatus(e.g.,delivered,failed,rejected,accepted), along with timestamps and potential error details.200 OKresponse to acknowledge receipt.TODO) for updating your application's message records.GETroute for basic verification that the server is running.4. Test SMS delivery status webhooks and DLRs
Bring it all together and test the flow.
1. Ensure ngrok is Running: Verify that your
ngrok http 3000command is still running in its terminal window and that theForwardingURL matches the one configured in your Vonage Application's webhook settings. If it stopped, restart it and update the URLs in Vonage if the random subdomain changed (unless you have a paid ngrok plan with a static domain).2. Fill
.envFile: Make sure you have filled in all the values in your.envfile:VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_ID,VONAGE_NUMBER, and a validTO_NUMBERfor testing. Remember to use the E.164 format for phone numbers (e.g.,14155552671).3. Start the Node.js Server: In your primary terminal window (the one where you created the files and installed dependencies), run:
You should see output like:
4. Test Sending an SMS: Open a third terminal window or use a tool like Postman or
curlto send a POST request to your/send-smsendpoint.Using
curl:Replace
YOUR_TEST_RECIPIENT_NUMBERwith the number you set inTO_NUMBERor any other valid number.server.js, you should see:Attempting to send SMS from <VONAGE_NUMBER> to <TO_NUMBER>: "Hello from Vonage Node.js Application!"SMS submitted successfully: { message_uuid: '…' }curlOutput: You should receive a JSON response like:{"message":"SMS sent successfully!","message_uuid":"…"}.5. Test Delivery Status Webhook: Wait a few seconds after the message is delivered to your phone.
--- Delivery Status Update Received ---Request Body: { … "status": "delivered", "message_uuid": "…", … }(or potentiallyacceptedfirst, thendelivered). The exact status flow can vary slightly.UUID: …, Status: delivered, Timestamp: …6. Test Inbound SMS Webhook: Using the phone that received the test message (or any phone), send an SMS message to your Vonage virtual number (the one specified in
VONAGE_NUMBER).--- Inbound SMS Received ---Request Body: { "from": { "type": "sms", "number": "<SENDER_NUMBER>" }, "text": "<YOUR_MESSAGE_TEXT>", … }From: <SENDER_NUMBER>, Text: <YOUR_MESSAGE_TEXT>, UUID: …, Timestamp: …If all these steps work and you see the corresponding logs, your core functionality is correctly implemented!
5. Handle webhook errors and implement retry logic for production
While our basic example includes
try...catchandconsole.log/console.error, production applications need more robust strategies. This involves replacing the basic console logging with a structured logger like Winston for better analysis and handling errors gracefully.Error Handling Strategy:
vonage.messages.send()call. Inspecterr.response.dataorerr.messagefor details from the Vonage API. Return appropriate HTTP status codes (e.g., 400 for invalid input to Vonage, 401 for auth issues, 500/503 for Vonage server issues). Log these errors using a structured logger./webhooks/inbound,/webhooks/status) intry...catchblocks. Log any errors encountered during processing (e.g., database write failure) using the logger. Crucially, still return a200 OKresponse to Vonage unless the request itself is malformed (which is unlikely for valid Vonage webhooks). Acknowledging the webhook prevents Vonage retries for processing errors that Vonage can't fix. Handle the processing failure asynchronously (e.g., add to a retry queue)./send-sms) and webhook payloads (check for expected fields) early and return400 Bad Requestif invalid. Log validation failures.Logging:
Using a dedicated logging library like Winston or Pino is highly recommended over
console.log/console.errorfor production applications.console.*: The primary goal is to replace all instances ofconsole.logandconsole.errorin yourserver.js(specifically within the route handlers like/send-sms,/webhooks/inbound,/webhooks/status) with calls to a logger instance (e.g.,logger.info,logger.error).debug,info,warn,error. Log informational messages (like successful sends, received webhooks) atinfo, potential issues atwarn, and definite errors aterror. Usedebugfor verbose tracing during development.message_uuid,request_id,endpoint, etc.Example using Winston (Basic Setup):
npm install winstonserver.js:Integrating the Logger:
Go back through the
server.jscode provided in Section 3 and systematically replace everyconsole.log(...)withlogger.info(...)and everyconsole.error(...)withlogger.error(...). Ensure you pass relevant contextual information as the second argument (metadata object) to the logger methods, as shown in the examples above. This provides much richer and more useful logs for debugging and monitoring.Retry Mechanisms (Webhook Processing):
Vonage automatically retries sending webhooks if it doesn't receive a
200 OKresponse within a certain timeout (usually a few seconds). However, this only handles network issues or your server being temporarily down. It doesn't handle errors within your webhook processing logic (like a database connection failure).For robust processing:
res.status(200).send('OK')immediately after receiving and basic validation of the webhook.async-retryor queue-specific features can help.This ensures your webhook endpoints remain responsive to Vonage while handling transient processing failures gracefully. For simpler applications, logging the error and potentially alerting might suffice, accepting that some webhook events might be lost if processing fails persistently.
6. Store SMS delivery status in a database for tracking
Storing message details and status is often necessary. Here's a conceptual schema and considerations.
Database Choice: PostgreSQL, MySQL, MongoDB, or others depending on needs. Relational databases (Postgres, MySQL) are often suitable for structured message data.
Conceptual Schema (Relational Example):
Data Layer Implementation:
vonage.messages.send(), create a record insms_messageswithdirection = 'outbound',status = 'submitted',recipient_number,message_body, etc. Store the intendedmessage_uuidif you generate one client-side, or leave it NULL.vonage.messages.send()call, update the record with themessage_uuidreturned by Vonage./webhooks/status):message_uuid,status,timestamp, anderrordetails.sms_messagesusingmessage_uuid.status,status_updated_at,vonage_status_code, andvonage_status_reason. Handle potential race conditions if multiple status updates arrive for the same UUID (e.g., check timestamps)./webhooks/inbound):from.number,text,message_uuid,timestamp.sms_messageswithdirection = 'inbound',status = 'received',sender_number = from.number,message_body = text,received_at = timestamp, and themessage_uuid.Migrations: Use database migration tools (like Sequelize CLI, TypeORM CLI, Prisma Migrate) to manage schema changes version control.
Performance: Index columns frequently used in
WHEREclauses (message_uuid,status,direction, timestamps).Related SMS integration tutorials