This guide provides a step-by-step walkthrough for building a production-ready Node.js application using the Express framework to handle two-way SMS messaging with the Vonage Messages API. You will learn how to send outbound SMS messages and receive and process inbound SMS messages via webhooks.
Project Overview and Goals
What We're Building:
An Express.js application capable of:
- Sending SMS: Exposing an API endpoint to send SMS messages programmatically via the Vonage Messages API.
- Receiving SMS: Handling incoming SMS messages sent to a Vonage virtual number via webhooks.
- Two-Way Interaction: Replying automatically to inbound messages, demonstrating a basic two-way conversation flow.
Problem Solved:
This application enables businesses and developers to integrate programmatic SMS communication into their workflows, facilitating automated notifications, customer support interactions, alerts, and more, directly from their Node.js backend.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express.js: A minimal and flexible Node.js web application framework used to build the API and handle webhook requests.
- Vonage Messages API: A unified API for sending and receiving messages across various channels, including SMS. We will use it for both outbound and inbound SMS.
- @vonage/server-sdk: The official Vonage Node.js SDK for interacting with Vonage APIs.
- ngrok: A tool to expose local development servers to the internet, essential for testing Vonage webhooks locally.
- dotenv: A module to load environment variables from a
.env
file intoprocess.env
.
System Architecture:
+-----------------+ +-----------------------+ +----------------+ +-------------+
| User/Client App | ---> | Node.js/Express App | ---> | Vonage API | ---> | User's Phone|
| (e.g., curl, UI)| | (Sends SMS, | | (Messages API) | | (Receives SMS)|
| | <--- | Handles Webhooks) | <--- | | <--- | (Sends SMS) |
+-----------------+ +-----------------------+ +----------------+ +-------------+
| ^ |
| API Call | | Webhook Notification
v +-----+
+-----------------------+
| Vonage Application |
| (Configured Webhooks) |
+-----------------------+
Prerequisites:
- Vonage API Account: Sign up at Vonage.com. You'll need your API Key and Secret.
- Vonage Virtual Number: Purchase an SMS-capable number from the Vonage Dashboard.
- Node.js and npm: Installed on your development machine (LTS version recommended). Download Node.js
- ngrok: Installed and authenticated. Download ngrok
- Basic JavaScript/Node.js Knowledge: Familiarity with asynchronous programming (
async
/await
or Promises). - Terminal/Command Line Access: For running commands.
Expected Outcome:
By the end of this guide_ you will have a running Node.js Express application that can send SMS messages via an API call and automatically reply to any SMS messages received on your configured Vonage number.
1. Setting Up the Project
Let's initialize the Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for your project_ then navigate into it.
mkdir vonage-sms-app cd vonage-sms-app
-
Initialize npm: Initialize the project with npm to create a
package.json
file. The-y
flag accepts default settings.npm init -y
-
Install Dependencies: Install Express_ the Vonage Server SDK_ and dotenv.
npm install express @vonage/server-sdk dotenv
-
Create
.gitignore
: Create a.gitignore
file to prevent sensitive information and unnecessary files from being committed to version control.touch .gitignore
Add the following lines to your
.gitignore
file:# .gitignore # Node dependencies node_modules/ # Environment variables .env # Private Key file (if stored in project) private.key # Log files *.log # OS generated files .DS_Store Thumbs.db
-
Set Up Environment Variables: Create a
.env
file in the root of your project to store sensitive credentials and configuration.touch .env
Populate
.env
with the following variables. We'll fill in the values in the next section.# .env # Vonage API Credentials (Found in your Vonage Dashboard) VONAGE_API_KEY=YOUR_VONAGE_API_KEY VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET # Vonage Application Credentials (Generated in the next step) VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Or the full path to your key # Vonage Number (Purchased from Vonage Dashboard) VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Application Port PORT=3000
- Why
.env
? Storing configuration and secrets in environment variables is a best practice. It keeps sensitive data out of your codebase and makes configuration easier across different environments (development_ staging_ production).dotenv
helps load these variables during development.
- Why
-
Create Basic Server File: Create an
app.js
file for your Express application logic.touch app.js
Add the initial Express setup:
// app.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const { json_ urlencoded } = express; const app = express(); app.use(json()); // Middleware to parse JSON request bodies app.use(urlencoded({ extended: true })); // Middleware to parse URL-encoded request bodies const PORT = process.env.PORT || 3000; // Use port from .env or default to 3000 // Basic route for testing app.get('/'_ (req_ res) => { res.send('Vonage SMS App is running!'); }); app.listen(PORT, () => { console.log(`Server listening at http://localhost:${PORT}`); });
-
Run the Basic Server: Start your server to ensure the basic setup is working.
node app.js
You should see
Server listening at http://localhost:3000
in your terminal. You can stop the server withCtrl+C
.
2. Vonage Account and Application Setup
Now, let's configure your Vonage account, get the necessary credentials, and set up a Vonage Application to handle messages.
- Log in to Vonage Dashboard: Access your Vonage API Dashboard.
- Get API Key and Secret: Your API Key and Secret are displayed at the top of the dashboard home page. Copy these values and paste them into your
.env
file forVONAGE_API_KEY
andVONAGE_API_SECRET
. - Purchase a Vonage Number:
- Navigate to Numbers -> Buy numbers.
- Search for a number with SMS capabilities in your desired country.
- Purchase the number.
- Copy this number (including the country code, e.g.,
14155550100
) and paste it into your.env
file forVONAGE_NUMBER
.
- Set Default SMS API to Messages API:
- Navigate to Account -> API settings.
- Scroll down to the SMS settings section.
- Under Default SMS Setting, select Messages API.
- Click Save changes.
- Why? Vonage has multiple APIs for SMS. The Messages API is the modern, unified API that supports multiple channels. Setting it as default ensures consistency and that webhooks use the Messages API format.
- Create a Vonage Application:
- Navigate to Applications -> Create a new application.
- Give your application a descriptive name (e.g.,
Node Two-Way SMS App
). - Click Generate public and private key. This will automatically download a
private.key
file. Save this file securely. Move it to the root of your project directory (or another secure location). UpdateVONAGE_PRIVATE_KEY_PATH
in your.env
file to point to its location (e.g.,./private.key
if it's in the project root). - Enable the Messages capability.
- You'll need to enter Inbound URL and Status URL webhooks. For now, you can enter placeholder URLs like
http://example.com/webhooks/inbound
andhttp://example.com/webhooks/status
. We will update these later with our ngrok URL. - Click Generate new application.
- After creation, you'll see the Application ID. Copy this ID and paste it into your
.env
file forVONAGE_APPLICATION_ID
.
- Link Your Number to the Application:
- Go back to the Applications list and find the application you just created.
- Click Link next to the Vonage number you purchased earlier.
- Why? This tells Vonage that any messages received on this specific number should be handled by this application and its configured webhooks.
3. Implementing Outbound SMS (Sending)
Let's add the functionality to send SMS messages via an API endpoint.
-
Initialize Vonage SDK: In
app.js
, initialize the Vonage SDK using the credentials from your environment variables. Place this near the top, afterrequire('dotenv').config();
.// app.js // ... (dotenv, express requires) const { Vonage } = require('@vonage/server-sdk'); // Initialize Vonage const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, apiSecret: process.env.VONAGE_API_SECRET, // Note: Application ID and Private Key are configured in .env and are crucial // for linking the number and ensuring Vonage directs incoming webhooks correctly, // even though they might not be explicitly required in this SDK client instance // *just* for sending SMS via Key/Secret authentication. // applicationId: process.env.VONAGE_APPLICATION_ID, // privateKey: process.env.VONAGE_PRIVATE_KEY_PATH }, { debug: true }); // Enable debug logging for Vonage SDK // ... (express app setup)
- Note on Authentication: We initialize the SDK using the API Key and Secret, which are sufficient for sending SMS via the Messages API as demonstrated in this guide. The Application ID and Private Key (set in
.env
) are essential for Vonage to correctly associate your number with this application and route incoming message webhooks.
- Note on Authentication: We initialize the SDK using the API Key and Secret, which are sufficient for sending SMS via the Messages API as demonstrated in this guide. The Application ID and Private Key (set in
-
Create Sending Function: It's good practice to encapsulate the sending logic in a function.
// app.js // ... (Vonage initialization) async function sendSms(to, text) { const from = process.env.VONAGE_NUMBER; // Your Vonage number from .env try { const resp = await vonage.messages.send({ message_type: ""text"", text: text, to: to, // The recipient's phone number from: from, // Your Vonage virtual number channel: ""sms"" }); console.log(""Message sent successfully:"", resp.message_uuid); return { success: true, message_uuid: resp.message_uuid }; } catch (err) { console.error(""Error sending SMS:"", err.response ? err.response.data : err.message); // Log more detail if available from Vonage response if (err.response && err.response.data) { console.error(""Vonage Error Details:"", JSON.stringify(err.response.data, null, 2)); } return { success: false, error: err.message }; } } // ... (express app setup)
- Why
async
/await
? Thevonage.messages.send
method is asynchronous (it makes a network request).async
/await
provides a cleaner way to handle promises compared to.then()
/.catch()
chains. - Parameters Explained:
message_type: ""text""
: Specifies a standard text message.text
: The content of the SMS.to
: The recipient's phone number in E.164 format (e.g.,14155550101
).from
: Your Vonage virtual number (must be linked to your app if required by regulations).channel: ""sms""
: Explicitly specifies the SMS channel.
- Why
-
Create API Endpoint for Sending: Add a POST route to your Express app to trigger the
sendSms
function.// app.js // ... (sendSms function) // API Endpoint to send SMS app.post('/send-sms', async (req, res) => { const { to, text } = req.body; if (!to || !text) { return res.status(400).json({ error: 'Missing required fields: to, text' }); } // Basic validation (can be more robust) if (!/^\d+$/.test(to.replace(/^\+/, ''))) { return res.status(400).json({ error: 'Invalid ""to"" phone number format.' }); } const result = await sendSms(to, text); if (result.success) { res.status(200).json({ message: 'SMS sent successfully', message_uuid: result.message_uuid }); } else { res.status(500).json({ error: 'Failed to send SMS', details: result.error }); } }); // ... (app.listen)
- Input Validation: Basic checks are included for required fields (
to
,text
) and a simple format check for theto
number. Production apps should have more robust validation.
- Input Validation: Basic checks are included for required fields (
-
Test Sending:
-
Restart your Node.js server:
node app.js
. -
Open a new terminal window or use a tool like Postman/Insomnia.
-
Send a POST request using
curl
:curl -X POST http://localhost:3000/send-sms \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""YOUR_PERSONAL_PHONE_NUMBER"", ""text"": ""Hello from Vonage Node App!"" }'
- Replace
YOUR_PERSONAL_PHONE_NUMBER
with your actual mobile number in E.164 format (e.g.,14155550101
).
- Replace
-
You should receive an SMS on your phone, and see success logs in your Node.js console and a JSON response in your
curl
terminal.
-
4. Implementing Inbound SMS (Receiving)
To receive messages, Vonage needs a publicly accessible URL (a webhook) to send HTTP POST requests to when your Vonage number gets an SMS. We'll use ngrok
for local development.
-
Start ngrok: If your app is running on port 3000 (as configured in
.env
or defaulted inapp.js
), run ngrok to expose this port. Whilengrok
is excellent for local development, for staging or production environments where you have a publicly accessible server, you would use your server's actual public URL instead. Other tunneling services (likelocaltunnel
or cloud provider specific tools) also exist.# Make sure your Node app is running in another terminal: node app.js ngrok http 3000
ngrok will provide a
Forwarding
URL (e.g.,https://<random-string>.ngrok.io
). Copy thehttps
version of this URL. -
Update Vonage Application Webhooks:
- Go back to your Vonage Application settings in the dashboard (Applications -> Your App -> Edit).
- Update the Messages webhooks:
- Inbound URL: Paste your ngrok
https
URL, adding/webhooks/inbound
at the end (e.g.,https://<random-string>.ngrok.io/webhooks/inbound
). Set the method to POST. - Status URL: Paste your ngrok
https
URL, adding/webhooks/status
at the end (e.g.,https://<random-string>.ngrok.io/webhooks/status
). Set the method to POST.
- Inbound URL: Paste your ngrok
- Click Save changes.
- Why both?
Inbound URL
: Receives data about incoming messages (SMS sent to your Vonage number).Status URL
: Receives delivery receipts and status updates for outgoing messages (SMS sent from your Vonage number).
-
Create Webhook Handlers in Express: Add POST routes in
app.js
to handle requests from Vonage.// app.js // ... (after /send-sms route) // Webhook endpoint for incoming SMS messages app.post('/webhooks/inbound', (req, res) => { console.log('--- Inbound Message ---'); console.log(JSON.stringify(req.body, null, 2)); // Log the full inbound payload // TODO: Process the inbound message (e.g., reply) // Vonage needs a 200 OK response to know the webhook is working res.status(200).end(); }); // Webhook endpoint for SMS status updates app.post('/webhooks/status', (req, res) => { console.log('--- Message Status ---'); console.log(JSON.stringify(req.body, null, 2)); // Log the status update // TODO: Process the status update (e.g., track delivery) // Respond with 200 OK res.status(200).end(); }); // ... (app.listen)
- Crucial: Always respond with a
200 OK
status to Vonage webhooks promptly. If Vonage doesn't receive a200 OK
, it will assume the delivery failed and may retry, leading to duplicate processing. - Logging: Logging the full
req.body
is essential for understanding the data structure Vonage sends.
- Crucial: Always respond with a
-
Test Receiving:
- Make sure your Node.js app (
node app.js
) and ngrok (ngrok http 3000
) are running. - From your personal mobile phone, send an SMS message to your Vonage virtual number (the one in
VONAGE_NUMBER
). - Check your Node.js console. You should see the ""--- Inbound Message ---"" log followed by a JSON object containing details about the SMS you sent (sender number, text content, timestamp, etc.).
- Example Inbound Payload (
req.body
):{ ""to"": ""18335787204"", ""from"": ""19999999999"", ""channel"": ""sms"", ""message_uuid"": ""a580f869-e995-4d76-9b80-a7befe3186a3"", ""timestamp"": ""2022-12-07T23:04:32Z"", ""usage"": { ""price"": ""0.0057"", ""currency"": ""EUR"" }, ""message_type"": ""text"", ""text"": ""Hello Vonage!"", ""sms"": { ""num_messages"": ""1"" } }
- You can also inspect the request details in the ngrok web interface, usually accessible at
http://127.0.0.1:4040
.
- Make sure your Node.js app (
5. Implementing Two-Way Logic (Auto-Reply)
Now, let's connect the inbound handler to the outbound function to create an auto-reply bot.
-
Modify Inbound Webhook Handler: Update the
/webhooks/inbound
route to extract the sender's number and message, then callsendSms
to reply.// app.js // Webhook endpoint for incoming SMS messages app.post('/webhooks/inbound', async (req, res) => { // Make the handler async console.log('--- Inbound Message ---'); console.log(JSON.stringify(req.body, null, 2)); const inboundData = req.body; // Ensure it's an inbound SMS text message if (inboundData.channel === 'sms' && inboundData.message_type === 'text') { const sender = inboundData.from; const messageText = inboundData.text; console.log(`Received message ""${messageText}"" from ${sender}`); // Simple auto-reply logic const replyText = `You said: ""${messageText}"". Thanks for messaging!`; // Send the reply using the existing sendSms function await sendSms(sender, replyText); } else { console.log('Received non-SMS or non-text message, skipping reply.'); } // Always respond with 200 OK res.status(200).end(); }); // ... (rest of the code)
- Key Changes:
- The handler is now
async
to allowawait
ing thesendSms
call. - We extract
inboundData.from
(the sender's number) andinboundData.text
. - We call
sendSms
with the original sender as the new recipient (to
).
- The handler is now
- Key Changes:
-
Test Two-Way Messaging:
- Restart your Node.js app (
node app.js
). Ensure ngrok is still running. - Send another SMS from your personal phone to your Vonage number.
- You should see the inbound message logged in your Node console.
- Shortly after, you should receive an SMS reply on your personal phone like:
""You said: ""<Your Message>"". Thanks for messaging!""
. - Check the Node console again; you should see logs for both the inbound message and the status of the outbound reply message (coming to the
/webhooks/status
endpoint).
- Restart your Node.js app (
6. Error Handling and Logging
Robust error handling and clear logging are crucial for production applications.
-
Vonage SDK Errors: The
try...catch
block insendSms
already handles errors from the Vonage API. Loggingerr.response.data
provides specific Vonage error details when available. -
Webhook Errors:
- Ensure your webhook endpoints (
/webhooks/inbound
,/webhooks/status
) always return200 OK
. Usetry...catch
within these handlers if performing complex logic that might fail, but ensure theres.status(200).end()
is called even if an internal error occurs during processing. - Log any processing errors clearly within the webhook handlers.
- Ensure your webhook endpoints (
-
Enhanced Logging: For production, consider using a structured logging library like
winston
orpino
instead ofconsole.log
. This enables better log parsing, filtering, and integration with log management systems.npm install winston
// Example using Winston (replace console.log calls) const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ // - Write all logs with importance level of `error` or less to `error.log` // - Write all logs with importance level of `info` or less to `combined.log` new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), ], }); // If we're not in production then log to the `console` if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple(), })); } // Replace console.log('...') with logger.info('...') // Replace console.error('...') with logger.error('...') // --- Example Usage --- // logger.info('Server started'); // logger.error('Failed to send SMS', { error: err.message });
- Placement: This logger setup code should typically be placed early in your application startup, for instance, near the top of your
app.js
file after requiring modules, or encapsulated within a separate logging module (logger.js
) that you import where needed.
- Placement: This logger setup code should typically be placed early in your application startup, for instance, near the top of your
-
Retry Mechanisms: Vonage automatically retries sending webhook notifications if it doesn't receive a
200 OK
. For outgoing SMS failures (e.g., network issues calling the Vonage API), you might implement your own retry logic within thesendSms
function'scatch
block, potentially using libraries likeasync-retry
with exponential backoff.
7. Security Considerations
- Environment Variables: Never commit your
.env
file or hardcode credentials (API Key
,Secret
,Private Key
) directly in your code. Use environment variables managed securely in your deployment environment. - Webhook Security (Signature Verification): Vonage can sign its webhook requests, allowing you to verify they genuinely came from Vonage. This prevents attackers from sending fake requests to your endpoints. Implementing signature verification (using JWT or Signed Webhooks based on Vonage settings) is highly recommended for production. Refer to the Vonage Webhook Security Documentation for implementation details using the SDK. The
@vonage/server-sdk
often includes helper functions or utilities to simplify this verification process; consult the SDK's documentation for specific methods related to webhook signature validation. - Input Validation: Sanitize and validate all input, especially the
text
from incoming SMS messages, if you plan to store it, display it, or use it in further processing (e.g., database queries) to prevent injection attacks. Libraries likeexpress-validator
can help. - Rate Limiting: Protect your
/send-sms
endpoint (if publicly exposed) and potentially your webhook endpoints from abuse by implementing rate limiting using middleware likeexpress-rate-limit
. - Private Key Security: Treat your
private.key
file like any other secret. Ensure its file permissions are restricted and it's stored securely on your server.
8. Testing
Comprehensive testing ensures your application works reliably.
-
Manual Testing (as performed above):
- Use
curl
/Postman to test the/send-sms
endpoint. Verify SMS delivery. - Send SMS to your Vonage number. Verify logs and the auto-reply.
- Use
-
Automated Testing (Unit/Integration):
- Unit Tests: Use frameworks like Jest or Mocha/Chai to test individual functions (e.g., the logic inside your webhook handlers, input validation) in isolation. Mock the Vonage SDK to avoid actual API calls during unit tests.
- Integration Tests: Test the interaction between your API endpoints, the Vonage SDK (potentially mocked or hitting a Vonage sandbox if available), and your internal logic. Tools like
supertest
are excellent for testing Express routes.
Before writing tests that require the Express app instance, ensure you export it from your main application file (
app.js
). Add the following line at the bottom ofapp.js
:module.exports = app;
# Install testing dependencies npm install --save-dev jest supertest
Example Jest/Supertest Integration Test (
app.test.js
):// app.test.js const request = require('supertest'); // Assuming your app.js exports the express app instance (see instruction above) const app = require('./app'); // Adjust path if needed // Mock the Vonage SDK to avoid real API calls jest.mock('@vonage/server-sdk', () => { const mockMessages = { send: jest.fn().mockResolvedValue({ message_uuid: 'mock-uuid-12345' }) }; return { Vonage: jest.fn().mockImplementation(() => ({ messages: mockMessages })) }; }); describe('SMS Endpoints', () => { it('should return 400 if ""to"" is missing for /send-sms', async () => { const res = await request(app) .post('/send-sms') .send({ text: 'Test message' }); expect(res.statusCode).toEqual(400); expect(res.body.error).toContain('Missing required fields'); }); it('should return 200 and mock UUID for valid /send-sms', async () => { const res = await request(app) .post('/send-sms') .send({ to: '15551234567', text: 'Test message' }); expect(res.statusCode).toEqual(200); expect(res.body.message_uuid).toEqual('mock-uuid-12345'); }); it('should return 200 for /webhooks/inbound POST', async () => { const res = await request(app) .post('/webhooks/inbound') .send({ // Simulate Vonage inbound payload channel: 'sms', message_type: 'text', from: '15559876543', to: process.env.VONAGE_NUMBER || '18885551212', // Use env var or a default test number text: 'Hello inbound test' }); expect(res.statusCode).toEqual(200); // You could add more assertions here to check if the mocked sendSms was called }); it('should return 200 for /webhooks/status POST', async () => { const res = await request(app) .post('/webhooks/status') .send({ message_uuid: 'some-uuid', status: 'delivered' }); expect(res.statusCode).toEqual(200); }); });
Add to
package.json
scripts:""scripts"": { ""start"": ""node app.js"", ""test"": ""jest"" }
Run tests:
npm test
9. Deployment
Deploying requires moving your app from your local machine to a server accessible on the internet.
- Choose a Hosting Provider: Options include PaaS (Heroku, Render, Fly.io), IaaS (AWS EC2, Google Compute Engine, DigitalOcean Droplets), or Serverless platforms.
- Environment Variables: Configure your production environment variables (
VONAGE_API_KEY
,VONAGE_API_SECRET
,VONAGE_APPLICATION_ID
,VONAGE_PRIVATE_KEY_PATH
,VONAGE_NUMBER
,PORT
,NODE_ENV=production
) securely using your hosting provider's interface. Do not commit.env
to production. - Private Key: Securely transfer your
private.key
file to the production server or use a secrets management service. Ensure theVONAGE_PRIVATE_KEY_PATH
environment variable points to the correct location on the server. - Update Vonage Webhooks: Once your application is deployed and has a public URL (e.g.,
https://your-app-name.herokuapp.com
), update the Inbound URL and Status URL in your Vonage Application settings to point to your production endpoints (e.g.,https://your-app-name.herokuapp.com/webhooks/inbound
). Remove the ngrok URLs. - Build/Deployment Process: Follow your hosting provider's instructions. This typically involves pushing your code to Git, connecting the repository to the provider, and triggering a build/deploy. Ensure
npm install --omit=dev
(or similar command) is run in production to avoid installing development dependencies. - Process Manager: Use a process manager like
pm2
or your platform's built-in mechanism (e.g., Heroku Dynos) to keep your Node.js application running reliably and restart it if it crashes.# Install pm2 globally on the server (if needed) npm install pm2 -g # Start your app pm2 start app.js --name vonage-sms-app # Monitor pm2 list pm2 logs vonage-sms-app
- CI/CD (Optional but Recommended): Set up a Continuous Integration/Continuous Deployment pipeline (using GitHub Actions, GitLab CI, Jenkins, etc.) to automatically test and deploy your application whenever you push changes to your repository.
10. Troubleshooting and Caveats
- SMS Not Sending:
- Check Node console logs for errors from the Vonage SDK.
- Verify API Key/Secret in
.env
are correct. - Ensure the
to
number is in valid E.164 format. - Check your Vonage account balance.
- Confirm the
from
number (VONAGE_NUMBER
) is correct and SMS-capable. - Look for error details in the
err.response.data
if logged.
- Inbound Webhooks Not Triggering:
- Verify the Inbound URL in Vonage Application settings is exactly correct (HTTPS, correct path
/webhooks/inbound
, no typos). - Ensure your deployed application is running and accessible at the webhook URL. Check server logs.
- If using ngrok locally: Ensure ngrok is running and forwarding to the correct local port (
3000
). Ensure the ngrok URL in Vonage is the current active one (it changes each time you restart ngrok unless you have a paid plan with a static domain). Check the ngrok web interface (http://127.0.0.1:4040
) for requests. - Verify the Vonage number is correctly linked to the Vonage Application.
- Check the Vonage Dashboard's API Logs (Logs -> API logs) for errors related to webhook delivery attempts.
- Verify the Inbound URL in Vonage Application settings is exactly correct (HTTPS, correct path
- Receiving Multiple Inbound Messages (Retries):
- This happens if your webhook doesn't respond with
200 OK
quickly enough. Ensure your/webhooks/inbound
handler returnsres.status(200).end()
immediately, even if background processing continues. Offload long-running tasks to a background job queue if necessary.
- This happens if your webhook doesn't respond with
- Incorrect Default API: If webhooks have unexpected formats, double-check that the Messages API is set as the default SMS setting in your Vonage account API settings.