This guide provides a comprehensive walkthrough for building a Node.js application using the Express framework to send SMS messages via the Vonage Messages API and reliably receive delivery status updates through webhooks.
We will cover everything from initial project setup and configuration to implementing core functionality, handling webhooks, security considerations, and deployment strategies. By the end, you will have a robust system capable of sending SMS messages and tracking their delivery status.
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 the webhook endpoint.
- Vonage Messages API: A multi-channel API enabling communication via SMS, MMS, WhatsApp, and more. We'll use it specifically for 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.ngrok
: A tool to expose local development servers to the internet, necessary for receiving webhook callbacks during development.
System Architecture:
The system involves two main flows:
- Sending SMS: Your Node.js application uses the Vonage SDK to make an API call to Vonage, instructing it to send an SMS to a specified recipient.
- Receiving Delivery Status: Vonage receives status updates from the carrier (e.g.,
delivered
,failed
). If configured, Vonage sends an HTTP POST request (webhook) containing this status information to a specified URL hosted by your Express application.
+------------------------+ 1. Send SMS Request +-----------------+ SMS +-------------+
| Your Node.js/Express | ----------------------------> | Vonage API | -----------> | Carrier |
| Application | (SDK Call) | | | |
+------------------------+ +-----------------+ +-------------+
^ | |
| | 2. Status Update |
| 3. Delivery Status Webhook (POST Request) | (e.g., Delivered) |
+--------------------------------------------------------+----------------------+
Final Outcome:
- A Node.js script capable of sending SMS messages using your Vonage account.
- An Express server running locally, exposed via
ngrok
, ready to receive and log delivery status webhooks from Vonage. - Secure handling of API credentials and private keys using environment variables.
Prerequisites:
- Node.js and npm (or yarn): Installed on your system. (Download Node.js)
- Vonage API Account: Sign up for free if you don't have one. (Vonage Dashboard)
- You'll need your API Key and API Secret.
- You'll need a Vonage virtual phone number capable of sending SMS.
ngrok
: Installed and authenticated with your free account. (Download ngrok)- Vonage CLI (Optional but Recommended): For managing Vonage applications and numbers via the command line. Install globally:
npm install -g @vonage/cli
1. Setting Up the Project
This section covers initializing the Node.js project, installing dependencies, configuring environment variables, and setting up your Vonage account and application.
1.1. Initialize Project and Install Dependencies
-
Create Project Directory:
mkdir vonage-sms-status-guide cd vonage-sms-status-guide
-
Initialize npm:
npm init -y
This creates a
package.json
file. -
Install Dependencies:
npm install @vonage/server-sdk express dotenv
(You can replace
npm install
withyarn add
if you prefer using Yarn.)@vonage/server-sdk
: The Vonage Node.js library.express
: Web framework for handling webhook requests.dotenv
: To manage environment variables securely.
1.2. Configure Environment Variables
Sensitive information like API keys should never be hardcoded. We'll use a .env
file.
-
Create
.env
file: Create a file named.env
in the root of your project directory (vonage-sms-status-guide
). -
Add Environment Variables: Open
.env
and add the following lines. We will fill these values in the next steps.# .env # Vonage API Credentials (Found in Vonage Dashboard) VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Details (Generated in Step 1.4) VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Relative path to your private key file # Vonage Number and Recipient VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER TO_NUMBER=RECIPIENT_PHONE_NUMBER # The number you want to send SMS to (e.g., your mobile)
Important: You must replace
YOUR_API_KEY
,YOUR_API_SECRET
,YOUR_APPLICATION_ID
,YOUR_VONAGE_VIRTUAL_NUMBER
, andRECIPIENT_PHONE_NUMBER
with your actual values obtained during the setup process. -
Create
.gitignore
: Ensure your.env
file and private key are not committed to version control. Create a.gitignore
file in the project root:# .gitignore node_modules .env private.key *.log
1.3. Configure Vonage Account Settings
Crucially, for this guide using the Messages API for sending and receiving status updates, you must ensure the Messages API is set as the default for SMS handling in your account settings.
- Log in to your Vonage API Dashboard.
- Navigate to Account -> Settings.
- Scroll down to the API settings section, then find SMS settings.
- Ensure
Default SMS Setting
is set toMessages API
. If it's set toSMS API
, change it and click Save changes.
1.4. Create a Vonage Application
Vonage Applications group your virtual numbers and configurations (like webhook URLs) and use a public/private key pair for authentication when using certain APIs like the Messages API.
- Navigate to Applications in the Vonage Dashboard.
- Click + Create a new application.
- Give your application a descriptive name (e.g.,
Node SMS Status Guide App
). - Click Generate public and private key. Immediately save the
private.key
file that downloads. Move this file into the root of your project directory (vonage-sms-status-guide
). - Enable the Messages capability.
- You'll see fields for Inbound URL and Status URL. We need a public URL for these, which we'll get from
ngrok
in the next step. Leave them blank for now, but keep this page open or note down the Application ID generated for this application. - Click Link next to the Vonage virtual number you want to use for sending SMS. Select the number and link it.
- Click Generate new application.
- Go back to your
.env
file and updateVONAGE_APPLICATION_ID
with the ID shown for your newly created application. Also, ensureVONAGE_PRIVATE_KEY_PATH
points to the location where you savedprivate.key
(e.g.,./private.key
). - Fill in
VONAGE_API_KEY
,VONAGE_API_SECRET
, andVONAGE_NUMBER
in your.env
file from your dashboard details. Choose aTO_NUMBER
(like your mobile) for testing.
ngrok
1.5. Expose Local Server with Webhooks require a publicly accessible URL. ngrok
creates a secure tunnel from the internet to your local machine.
-
Start
ngrok
: Open a new terminal window/tab (keep your project terminal open). Runngrok
, telling it to forward traffic to the port your Express server will run on (we'll use 3000).ngrok http 3000
-
Copy the Forwarding URL:
ngrok
will display session information, including aForwarding
URL that looks something likehttps://randomstring.ngrok.io
. Copy this HTTPS URL.
1.6. Configure Webhook URLs in Vonage Application
Now, link the public ngrok
URL to your Vonage Application's message status callbacks.
- Go back to your Vonage Application settings in the dashboard (Applications -> Your App Name -> Edit).
- In the Messages capability section:
- Set the Status URL to your
ngrok
HTTPS URL, appending/webhooks/status
. Example:https://randomstring.ngrok.io/webhooks/status
. - Set the Inbound URL (optional for this guide, but good practice): Set it to
https://randomstring.ngrok.io/webhooks/inbound
. This is where incoming SMS to your Vonage number would be sent. - Ensure the HTTP Method for both is set to POST.
- Set the Status URL to your
- Click Save changes.
2. Implementing Core Functionality: Sending SMS
Now let's write the Node.js code to send an SMS message using the Vonage Messages API and the credentials configured in .env
.
-
Create
send-sms.js
: Create a file namedsend-sms.js
in your project root. -
Add Code to Send SMS: Paste the following code into
send-sms.js
:// send-sms.js require('dotenv').config(); // Load environment variables from .env file const { Vonage } = require('@vonage/server-sdk'); const { MessagetypeText } = require('@vonage/messages'); // Import specific message type // --- Configuration --- // Ensure all required environment variables are set const requiredEnv = [ 'VONAGE_API_KEY', 'VONAGE_API_SECRET', 'VONAGE_APPLICATION_ID', 'VONAGE_PRIVATE_KEY_PATH', 'VONAGE_NUMBER', 'TO_NUMBER' ]; for (const envVar of requiredEnv) { if (!process.env[envVar]) { console.error(`Error: Environment variable ${envVar} is not set.`); process.exit(1); // Exit if configuration is missing } } const vonageApiKey = process.env.VONAGE_API_KEY; const vonageApiSecret = process.env.VONAGE_API_SECRET; const vonageAppId = 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; // --- Initialize Vonage Client --- // Use Application ID and Private Key for Messages API authentication const vonage = new Vonage({ apiKey: vonageApiKey, apiSecret: vonageApiSecret, // Though not strictly needed for JWT, good practice to include applicationId: vonageAppId, privateKey: vonagePrivateKeyPath }); // --- Send SMS Function --- async function sendSms(textMessage) { console.log(`Attempting to send SMS from ${vonageNumber} to ${toNumber}`); console.log(`Message: ""${textMessage}""`); try { // The vonage.messages.send function handles constructing the correct request const resp = await vonage.messages.send( new MessagetypeText({ // Use the specific text message class text: textMessage, to: toNumber, from: vonageNumber, channel: 'sms', // Explicitly specify the channel }), ); console.log('SMS submitted successfully!'); console.log('Message UUID:', resp.messageUuid); // Important ID for tracking return resp.messageUuid; } catch (err) { console.error('Error sending SMS:'); // Log specific details if available (e.g., response from Vonage) if (err.response && err.response.data) { console.error('Vonage API Error:', JSON.stringify(err.response.data, null, 2)); } else { console.error(err); } // Decide if the error is fatal or if the app can continue // For this script, we'll exit on failure process.exit(1); } } // --- Execute --- // Get message from command line argument or use default const message = process.argv[2] || `Hello from Vonage! Sent at ${new Date().toLocaleTimeString()}`; sendSms(message);
-
Explanation:
require('dotenv').config()
: Loads variables from your.env
file intoprocess.env
.- Configuration Check: Ensures all necessary environment variables are present before proceeding.
- Vonage Initialization: Creates a
Vonage
client instance. Crucially, for the Messages API, it usesapplicationId
andprivateKey
for authentication (JWT - JSON Web Token).apiKey
andapiSecret
might be used for other API calls or fallback. sendSms
Function:- Takes the message text as input.
- Uses
vonage.messages.send()
which is the correct method for the Messages API. - We instantiate
MessagetypeText
to structure the payload correctly, definingto
,from
,channel
, and the messagetext
. - Logs the
messageUuid
on successful submission. This ID is crucial for correlating the sent message with its delivery status later. - Includes detailed error logging, checking for specific Vonage API error responses.
-
Test Sending:
-
Make sure your
.env
file is filled correctly. -
Make sure
private.key
is in the project root. -
Run the script from your project terminal:
node send-sms.js ""This is a test message."" # Or without a message argument: # node send-sms.js
-
You should see
SMS submitted successfully!
and amessageUuid
logged in the console. -
You should receive the SMS on the
TO_NUMBER
phone shortly after.
-
3. Building the API Layer: Handling Status Webhooks
Now, let's create the Express server to listen for the delivery status webhook POST requests from Vonage.
-
Create
server.js
: Create a file namedserver.js
in your project root. -
Add Code for Express Server: Paste the following code into
server.js
:// server.js require('dotenv').config(); // Load environment variables if needed elsewhere (optional here) const express = require('express'); const { json, urlencoded } = express; // Import body-parsing middleware // --- Configuration --- const PORT = process.env.PORT || 3000; // Use environment port or default to 3000 // --- Initialize Express App --- const app = express(); // --- Middleware --- // Parse JSON request bodies (Vonage webhooks send JSON) app.use(json()); // Parse URL-encoded request bodies (less common for Vonage webhooks, but good practice) app.use(urlencoded({ extended: true })); // --- Webhook Endpoint --- // Define the route matching the Status URL configured in Vonage app.post('/webhooks/status', (req, res) => { console.log('--- Delivery Status Webhook Received ---'); console.log('Timestamp:', new Date().toISOString()); console.log('Request Body:', JSON.stringify(req.body, null, 2)); // Log the entire payload // Extract key information (adjust based on actual payload structure) const { message_uuid, status, timestamp, to, from, error, usage } = req.body; console.log(`Message UUID: ${message_uuid}`); console.log(`Status: ${status}`); // e.g., 'delivered', 'failed', 'accepted', 'buffered' console.log(`To: ${to}`); console.log(`From: ${from}`); if (timestamp) { console.log(`Status Timestamp: ${timestamp}`); } if (error) { console.error(`Error Code: ${error.code}`); console.error(`Error Reason: ${error.reason}`); } if (usage) { console.log(`Usage Price: ${usage.price} ${usage.currency}`); } // --- IMPORTANT: Acknowledge Receipt --- // Vonage expects a 2xx status code to confirm receipt. // Failure to send this will result in Vonage retrying the webhook. res.status(200).send('Webhook received successfully.'); // Alternatively, for no content response: res.sendStatus(204); }); // --- Health Check Endpoint (Good Practice) --- app.get('/health', (req, res) => { res.status(200).send('Server is healthy'); }); // --- Start Server --- app.listen(PORT, () => { console.log(`Server listening for webhooks on http://localhost:${PORT}`); console.log(`ngrok should be forwarding to this port.`); console.log(`Vonage Status URL should be configured to point to your ngrok URL + /webhooks/status`); });
-
Explanation:
- Initialization: Sets up a basic Express application.
- Middleware:
express.json()
is crucial for parsing the incoming JSON payload from Vonage webhooks intoreq.body
.express.urlencoded()
is included for completeness. /webhooks/status
Route:- Listens for POST requests specifically on the
/webhooks/status
path, matching the URL configured in the Vonage Application. - Logs the entire request body for inspection. This is vital during development to understand the payload structure.
- Extracts and logs key fields like
message_uuid
,status
,timestamp
,error
, etc. Note: The exact field names (message_uuid
,status
, etc.) are common but can occasionally vary. Always inspect the raw logged body (console.log('Request Body: ...')
) from an actual webhook delivery to confirm the structure before relying on specific fields. - Crucially, it sends back a
200 OK
response. This tells Vonage the webhook was received successfully. If Vonage doesn't receive a 2xx response, it will retry sending the webhook, leading to duplicate processing.
- Listens for POST requests specifically on the
/health
Route: A simple endpoint to check if the server is running, useful for monitoring.- Server Start: Starts the Express server listening on the specified
PORT
(matching the one used inngrok
).
-
Run the Server:
-
Make sure
ngrok
is still running and forwarding to port 3000. -
Open your primary project terminal (where
package.json
is). -
Run the server:
node server.js
-
You should see the
Server listening...
messages.
-
-
Test the Full Loop:
-
Keep
server.js
running in one terminal andngrok
in another. -
Open a third terminal (or reuse one) in the project directory.
-
Send another SMS using
send-sms.js
:node send-sms.js "Testing delivery receipt!"
-
Check the phone for the SMS.
-
Watch the terminal where
server.js
is running. Within a few seconds to a minute (depending on the carrier), you should see the--- Delivery Status Webhook Received ---
log, followed by the JSON payload containing themessage_uuid
of the message you just sent and itsstatus
(e.g.,submitted
,delivered
,buffered
, orfailed
).
-
4. Integrating with Vonage Service
This section summarizes the integration points covered in the setup and implementation steps.
- API Credentials:
VONAGE_API_KEY
andVONAGE_API_SECRET
are used primarily for account identification and potentially for APIs other than Messages/Voice that use basic auth. They are obtained from the main page of the Vonage API Dashboard. - Application ID & Private Key:
VONAGE_APPLICATION_ID
and theprivate.key
file (referenced byVONAGE_PRIVATE_KEY_PATH
) are essential for authenticating with the Messages API. They are generated when creating a Vonage Application in the dashboard. The private key must be stored securely and its path correctly specified in.env
. - SDK Initialization: The
@vonage/server-sdk
is initialized using the Application ID and the path to the private key file (new Vonage({ applicationId, privateKey: privateKeyPath })
). The SDK handles the JWT generation needed for authentication. - Webhook URLs: The
Status URL
configured in the Vonage Application settings (pointing to yourngrok
URL +/webhooks/status
) tells Vonage where to send delivery status updates via HTTP POST requests. Your Express server must listen on this specific path.
5. Error Handling, Logging, and Retry Mechanisms
5.1. Error Handling
-
Sending (
send-sms.js
):- The
try...catch
block captures errors during the API call. - It attempts to log specific error details from the Vonage response (
err.response.data
). - Includes a check for missing environment variables before attempting initialization.
- The
-
Receiving (
server.js
):- Basic logging of the entire webhook body helps diagnose issues.
- It's recommended to wrap the processing logic inside the webhook handler in a
try...catch
block to prevent the server from crashing due to unexpected payload formats and still send a200 OK
response.
// Example improvement in server.js app.post('/webhooks/status', (req, res) => { try { console.log('--- Delivery Status Webhook Received ---'); // ... (rest of the logging and processing) ... console.log('Processed webhook successfully.'); res.status(200).send('Webhook received successfully.'); } catch (error) { console.error('!!! Error processing webhook:'); console.error(error); // STILL send 200 OK to prevent Vonage retries for this specific processing error. // Log the error internally for investigation. res.status(200).send('Webhook received; internal processing error occurred.'); } });
5.2. Logging
- Sending: Logs submission attempts, success confirmation with
messageUuid
, and detailed errors. - Receiving: Logs the full webhook payload and key extracted fields.
- Production Logging: For production, consider using a dedicated logging library (like
winston
orpino
) to:- Log to files or centralized logging services (e.g., Datadog, Logstash, Splunk).
- Use structured logging (JSON format) for easier parsing and analysis.
- Control log levels (info, warn, error, debug).
5.3. Retry Mechanisms (Vonage Side)
- Vonage automatically retries sending webhooks if it doesn't receive a
2xx
response from your server within a timeout period. - This means your webhook endpoint must be idempotent – processing the same webhook multiple times should not cause unintended side effects (e.g., don't increment a counter every time the same
delivered
status for amessage_uuid
arrives). Check if you've already processed a specificmessage_uuid
with a terminal status before taking action. - Your server must respond quickly (ideally under 2 seconds) with a
2xx
status. Perform time-consuming tasks asynchronously after sending the response.
6. Database Schema and Data Layer (Conceptual)
In a production application, you'll want to store message information and status updates.
- Purpose: Track message history, correlate sent messages with status updates, analyze delivery rates, and potentially trigger business logic based on status changes.
- Conceptual Schema (
messages
table):id
(Primary Key, e.g., UUID or auto-incrementing integer)message_uuid
(VARCHAR, Unique): The ID received from Vonage upon sending. Crucial for correlation.to_number
(VARCHAR)from_number
(VARCHAR)message_body
(TEXT): The content of the sent message.submitted_at
(TIMESTAMP): When the message was submitted via the API.current_status
(VARCHAR): The latest status received ('submitted', 'delivered', 'failed', 'accepted', 'buffered', etc.).status_updated_at
(TIMESTAMP): When the latest status webhook was received.error_code
(VARCHAR, Nullable): If the status is 'failed', store the error code.error_reason
(TEXT, Nullable): Description of the error.usage_price
(DECIMAL, Nullable)usage_currency
(VARCHAR, Nullable)
- Implementation:
- Use an ORM (like Prisma, Sequelize, TypeORM) or a database client library (like
pg
,mysql2
). - Sending: After successfully calling
vonage.messages.send
, insert a new record into themessages
table with themessage_uuid
, recipient, sender, body, and initial status ('submitted'). - Receiving: In the
/webhooks/status
handler:- Extract the
message_uuid
andstatus
(and other relevant fields) fromreq.body
. - Find the corresponding record in the
messages
table usingmessage_uuid
. - Update the
current_status
,status_updated_at
, and any error/usage details. Ensure updates are idempotent. Check if the new status is different or logically follows the current status before updating. - Send the
200 OK
response immediately after receiving the request, potentially performing the database update asynchronously or ensuring it's very fast.
- Extract the
- Use an ORM (like Prisma, Sequelize, TypeORM) or a database client library (like
7. Security Features
- API Credentials:
- NEVER hardcode API keys, secrets, or private key paths. Use environment variables (
.env
locally, secure configuration management in deployment). - Ensure
.env
andprivate.key
are in your.gitignore
. - Restrict file permissions for
private.key
on servers.
- NEVER hardcode API keys, secrets, or private key paths. Use environment variables (
- Webhook Security:
- HTTPS: Always use HTTPS for your
ngrok
URL and production webhook endpoint to encrypt data in transit. Vonage requires HTTPS for webhook URLs. - Signature Validation (Challenge): The Vonage Messages API status/inbound webhooks do not currently support simple signed webhooks (like JWT or HMAC signatures found in some other Vonage APIs or services). Verifying that a request genuinely came from Vonage is more complex. Potential strategies (with tradeoffs):
- IP Whitelisting: Only allow requests from Vonage's published IP ranges. This requires maintenance as IPs can change. (See Vonage documentation for IPs).
- Secret in URL (Less Secure): Including a hard-to-guess secret token in the webhook URL path (
/webhooks/status/YOUR_SECRET_TOKEN
). This is obscurity, not true security. - Mutual TLS (Advanced): Requires more complex infrastructure setup.
- For many applications, the combination of required HTTPS and the inherent difficulty in guessing the unique webhook endpoint URL provides a practical level of security, although it doesn't cryptographically guarantee the sender's identity like signed webhooks would.
- HTTPS: Always use HTTPS for your
- Input Validation:
- Sanitize phone numbers (
TO_NUMBER
,VONAGE_NUMBER
) before use. Remove formatting, check length/prefix. - Validate the structure of incoming webhook payloads (
req.body
) before accessing nested properties.
- Sanitize phone numbers (
- Rate Limiting: Implement rate limiting on your webhook endpoint (using libraries like
express-rate-limit
) to prevent abuse if the endpoint URL is exposed.
8. Handling Special Cases
- Delivery Statuses: Be prepared to handle various statuses beyond just
delivered
andfailed
:submitted
: Message accepted by Vonage, not yet by the carrier.rejected
: Message rejected by Vonage (e.g., invalid number, insufficient funds).accepted
: Message accepted by the downstream carrier.buffered
: Message buffered by the downstream carrier (e.g., recipient device offline).expired
: Message failed to deliver within its validity period.unknown
: Final status could not be determined.
- DLR Delays: Delivery receipts can sometimes be delayed by carriers or arrive out of order. Your logic should handle this, primarily by relying on the
message_uuid
andtimestamp
within the DLR payload. - No DLR: Not all carriers or countries reliably provide DLRs. Your application should function correctly even if a final
delivered
orfailed
status is never received for some messages. Consider timeouts or assumptions after a certain period. FROM
Number Regulations: Be aware of country-specific regulations regarding sender IDs. In some countries (like the US), you must use a Vonage virtual number. In others, you might be able to use an Alphanumeric Sender ID (if pre-registered). Using an invalidFROM
number will cause sending failures.- Character Encoding: The Messages API generally handles common encodings, but ensure your message text is UTF-8. The SDK handles much of this.
9. Performance Optimizations
- Asynchronous Operations: Node.js is inherently asynchronous. All I/O operations (API calls, database interactions) should use
async/await
or Promises to avoid blocking the event loop. - Webhook Response Time: Respond to Vonage webhooks immediately with
200 OK
/204 No Content
. Offload any slow processing (database updates, further API calls) to a background job queue (e.g., BullMQ, Kue) or handle it asynchronously after sending the response. - SDK Client: Initialize the Vonage SDK client once when your application starts, rather than inside request handlers.
- Database Indexing: Ensure the
message_uuid
column in yourmessages
table is indexed for fast lookups when processing status webhooks.
10. Monitoring, Observability, and Analytics
- Health Checks: The
/health
endpoint allows load balancers or monitoring services (like UptimeRobot, Pingdom) to check if your server instance is running. - Logging: Centralized logging (as mentioned in Section 5) is crucial for observing behavior and diagnosing issues.
- Metrics: Track key metrics:
- Number of SMS sent successfully (API response).
- Number of SMS failed during API submission.
- Number of delivery status webhooks received per status (
delivered
,failed
,buffered
, etc.). - Webhook processing time (average, p95, p99).
- Webhook error rate (requests not returning 2xx, or internal processing errors).
- Error Tracking: Use services like Sentry or Bugsnag to capture and aggregate runtime errors in your Node.js application.
- Dashboards: Create dashboards (using tools like Grafana, Datadog, Kibana) to visualize the metrics above, providing an overview of system health and SMS delivery performance.
- Alerting: Set up alerts based on metrics and logs:
- High rate of sending failures.
- High rate of
failed
delivery statuses. - Webhook endpoint returning non-2xx status codes.
- Webhook processing time exceeding a threshold.
- Application crashes or high error rates reported by error tracking services.
11. Troubleshooting and Caveats
- Common Errors:
- Authentication Failure (401): Check
VONAGE_API_KEY
,VONAGE_API_SECRET
,VONAGE_APPLICATION_ID
, andVONAGE_PRIVATE_KEY_PATH
. Ensure the private key file exists and is readable. Make sure you are using the correct credentials for the API (Messages API uses App ID/Key). - Invalid
to
orfrom
Number (400 Bad Request): Verify phone number formats (E.164 recommended, e.g.,+14155552671
). Ensure theFROM
number is a Vonage number linked to your application and capable of sending SMS in the target country. - Insufficient Funds (420): Check your Vonage account balance.
- Throttling (429 Too Many Requests): You're exceeding the allowed API request rate. Implement backoff/retry logic or contact Vonage for rate limit increases.
- Webhook Not Received:
- Verify
ngrok
is running and forwarding to the correct port (3000
). - Verify the
Status URL
in the Vonage Application settings exactly matches thengrok
HTTPS URL +/webhooks/status
. - Check for firewall issues blocking
ngrok
or incoming connections. - Ensure the
server.js
application is running and hasn't crashed.
- Verify
- Vonage Retrying Webhooks: Your
server.js
is likely not sending a2xx
response, or it's taking too long to respond. Check logs for errors and ensureres.status(200).send()
is called promptly.
- Authentication Failure (401): Check
- Caveats: