Receive and respond to SMS messages seamlessly within your Next.js application using the Vonage Messages API. This guide provides a complete walkthrough, from setting up your Vonage account and Next.js project to implementing webhook handlers, sending replies, and deploying a production-ready solution.
We'll build a Next.js application capable of receiving incoming SMS messages sent to a Vonage virtual number via webhooks. The application will log these messages and demonstrate how to send replies. This solves the common need for applications to interact with users via SMS for notifications, alerts, customer support, or two-factor authentication follow-ups.
Project Overview and Goals
What We'll Build:
- A Next.js application with an API route acting as a webhook endpoint.
- Integration with the Vonage Messages API using the
@vonage/server-sdk
. - Functionality to receive incoming SMS messages sent to a dedicated Vonage number.
- Capability to log incoming message details.
- (Optional) Functionality to programmatically send SMS messages as replies or standalone messages.
Technologies Used:
- Next.js: A React framework providing server-side rendering, static site generation, and simplified API route creation – ideal for handling webhooks.
- Node.js: The JavaScript runtime environment underpinning Next.js.
- Vonage Messages API: Enables sending and receiving messages across various channels, including SMS.
@vonage/server-sdk
: The official Node.js library for interacting with Vonage APIs.ngrok
: A tool to expose local development servers to the internet, essential for testing webhooks.
System Architecture:
sequenceDiagram
participant User Phone
participant Vonage Platform
participant ngrok
participant Next.js App (API Route)
User Phone->>+Vonage Platform: Sends SMS to Vonage Number
Vonage Platform->>+ngrok: Forwards SMS data via HTTP POST (Webhook)
ngrok->>+Next.js App (API Route): Relays POST request to /api/webhooks/inbound
Next.js App (API Route)->>Next.js App (API Route): Processes inbound message (logs, etc.)
Next.js App (API Route)-->>-ngrok: Sends HTTP 200 OK response
ngrok-->>-Vonage Platform: Relays 200 OK response
Vonage Platform-->>-User Phone: (No direct response shown, SMS delivery confirmed)
%% Optional Reply Flow
Note over Next.js App (API Route): Logic decides to send a reply
Next.js App (API Route)->>+Vonage Platform: Calls Messages API (send SMS) using SDK
Vonage Platform->>+User Phone: Delivers reply SMS
User Phone-->>-Vonage Platform: (Receives SMS)
Vonage Platform-->>-Next.js App (API Route): Returns API response (message_uuid)
Prerequisites:
- Vonage API Account: Create one at Vonage API Dashboard. You'll need your API Key and Secret.
- Node.js: Version 18.x or later recommended. Download Node.js.
- npm or yarn: Node.js package manager, included with Node.js.
- A Vonage Phone Number: Purchase an SMS-enabled number through the Vonage Dashboard.
ngrok
: Install it globally or usenpx
. A free account is sufficient. ngrok Setup.- Basic understanding of Next.js and API routes.
Final Outcome:
By the end of this guide, you will have a functional Next.js application that reliably receives incoming SMS messages via Vonage webhooks and can be extended to send replies or perform other actions based on the message content.
1. Setting up the Project
Let's initialize a new Next.js project and install the necessary dependencies.
-
Create a Next.js App: Open your terminal and run the following command, replacing
vonage-nextjs-sms
with your desired project name. Choose your preferred settings when prompted (TypeScript recommended, App Router used here but adaptable for Pages Router).npx create-next-app@latest vonage-nextjs-sms
-
Navigate to Project Directory:
cd vonage-nextjs-sms
-
Install Vonage SDK: We need the Vonage server SDK to interact with the Messages API.
npm install @vonage/server-sdk
or using yarn:
yarn add @vonage/server-sdk
-
Set up Environment Variables: Sensitive credentials like API keys should never be hardcoded. We'll use environment variables. Create a file named
.env.local
in the root of your project.Terminal:
touch .env.local
Add the following variables to
.env.local
. We will populate these values in the ""Integrating with Vonage"" section..env.local:
# Vonage Credentials VONAGE_API_KEY=YOUR_VONAGE_API_KEY VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Include country code, e.g., 12015550123 # For sending tests (Optional) TO_NUMBER=YOUR_PERSONAL_PHONE_NUMBER # Include country code
Note on
VONAGE_PRIVATE_KEY_PATH
: Using a file path is convenient for local development. However, for deployment (covered in Section 11), embedding the private key content directly into an environment variable (e.g.,VONAGE_PRIVATE_KEY_CONTENT
) is often a more robust approach, especially in serverless environments.Important: Add
.env.local
and your private key file (e.g.,private.key
) to your.gitignore
file to prevent committing sensitive information..gitignore (ensure these lines exist):
# local environment variables .env*.local # Vonage private key private.key *.key
Why
.env.local
? Next.js automatically loads variables from this file intoprocess.env
for server-side code (like API routes), keeping your secrets secure and separate from your codebase.
2. Implementing Core Functionality (Receiving SMS)
We'll create a Next.js API route to act as the webhook endpoint Vonage will call when an SMS is received.
-
Create the API Route File: Create the necessary directories and the API route file.
Using App Router (default for
create-next-app
):mkdir -p app/api/webhooks/inbound touch app/api/webhooks/inbound/route.js
Using Pages Router (if selected during setup):
mkdir -p pages/api/webhooks touch pages/api/webhooks/inbound.js
-
Implement the Webhook Handler: Paste the following code into the
route.js
(App Router) orinbound.js
(Pages Router) file you created.app/api/webhooks/inbound/route.js (App Router):
import { NextResponse } from 'next/server'; export async function POST(request) { try { const inboundSms = await request.json(); // Vonage sends JSON payload console.log('--- Inbound SMS Received ---'); console.log('From:', inboundSms.from); console.log('To:', inboundSms.to); console.log('Text:', inboundSms.text); console.log('Message UUID:', inboundSms.message_uuid); console.log('Full Payload:', JSON.stringify(inboundSms, null, 2)); console.log('----------------------------'); // --- Add your custom logic here --- // Example: Store the message in a database, trigger another service, etc. // Example: Prepare data for a reply (see sending section) // Vonage requires a 200 OK response to acknowledge receipt. // Failure to send 200 OK will cause Vonage to retry the webhook. return new NextResponse(null, { status: 200 }); } catch (error) { console.error('Error processing inbound SMS:', error); // Return 500 Internal Server Error if processing fails return new NextResponse(JSON.stringify({ error: 'Failed to process webhook' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } } export async function GET(request) { // Optional: Handle GET requests for health checks or simple verification return new NextResponse(JSON.stringify({ message: 'Webhook endpoint is active. Use POST for inbound SMS.' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }
pages/api/webhooks/inbound.js (Pages Router):
// pages/api/webhooks/inbound.js export default async function handler(req, res) { if (req.method === 'POST') { try { const inboundSms = req.body; // Assumes body-parsing middleware is active (default in Next.js) console.log('--- Inbound SMS Received ---'); console.log('From:', inboundSms.from); console.log('To:', inboundSms.to); console.log('Text:', inboundSms.text); console.log('Message UUID:', inboundSms.message_uuid); console.log('Full Payload:', JSON.stringify(inboundSms, null, 2)); console.log('----------------------------'); // --- Add your custom logic here --- // Example: Store the message in a database, trigger another service, etc. // Vonage requires a 200 OK response to acknowledge receipt. res.status(200).end(); // Send 200 OK } catch (error) { console.error('Error processing inbound SMS:', error); res.status(500).json({ error: 'Failed to process webhook' }); } } else if (req.method === 'GET') { // Optional: Handle GET requests for health checks res.status(200).json({ message: 'Webhook endpoint is active. Use POST for inbound SMS.' }); } else { // Handle unsupported methods res.setHeader('Allow', ['POST', 'GET']); res.status(405).end(`Method ${req.method} Not Allowed`); } }
Why this code?
- It defines an API route at
/api/webhooks/inbound
. - It specifically handles
POST
requests, which is how Vonage sends webhook data. - It parses the incoming JSON payload (
request.json()
for App Router,req.body
for Pages Router). - It logs key information from the SMS message for debugging.
- Crucially, it sends back an empty
200 OK
response. This is vital to tell Vonage the message was received successfully and prevent retries. - Basic error handling logs issues and returns a
500
status code.
Note on Code Examples: For brevity, subsequent code modifications in this guide (e.g., adding logging, database integration) will primarily show the App Router version (
route.js
). The core logic can be adapted to the Pages Router structure (inbound.js
) usingreq
andres
objects. - It defines an API route at
3. Integrating with Vonage
Now, let's configure Vonage to work with our Next.js application.
-
Log in to Vonage: Access your Vonage API Dashboard.
-
Get API Credentials: On the main dashboard page, find your API key and API secret. Copy these values.
-
Update
.env.local
: Paste your API key and secret into theVONAGE_API_KEY
andVONAGE_API_SECRET
variables in your.env.local
file. -
Ensure Messages API is Default:
- Navigate to API Settings in the left-hand menu.
- Scroll down to the SMS settings section.
- Ensure ""Default SMS Setting"" is set to Messages API. If not, select it and click Save changes. This is crucial for the webhooks to have the correct format expected by our code.
-
Create a Vonage Application:
- Navigate to Applications > Create a new application.
- Give your application a descriptive name (e.g., ""NextJS SMS Handler"").
- Click Generate public and private key. This will automatically download a
private.key
file. Save this file securely. - Move the downloaded
private.key
file into the root directory of your Next.js project. The path./private.key
in.env.local
assumes it's in the root. - Enable the Messages capability by toggling it on.
- You will see fields for Inbound URL and Status URL. We will fill these in the next step using
ngrok
. Leave them blank for now or use a placeholder likehttp://localhost
. - Click Generate new application.
- You will be redirected to the application's page. Copy the Application ID.
-
Update
.env.local
: Paste the Application ID intoVONAGE_APPLICATION_ID
in your.env.local
file. -
Link Your Vonage Number:
- On the same application details page, scroll down to the Link virtual numbers section.
- Find the SMS-enabled Vonage number you purchased earlier.
- Click the Link button next to it.
- Enter your Vonage number (including country code, no symbols, e.g.,
12015550123
) into theVONAGE_NUMBER
variable in.env.local
. - Enter your personal phone number into
TO_NUMBER
if you plan to test sending messages.
-
Expose Local Server with
ngrok
:-
Start your Next.js development server (if not already running):
npm run dev
-
Open a new terminal window/tab in the same project directory.
-
Run
ngrok
to expose your local port 3000 (default for Next.js dev):ngrok http 3000
-
ngrok
will display output similar to this:Session Status online Account Your Name (Plan: Free) Version x.x.x Region United States (us-cal-1) Forwarding https://<random-string>.ngrok-free.app -> http://localhost:3000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
-
Copy the HTTPS Forwarding URL (e.g.,
https://<random-string>.ngrok-free.app
). Do not close this ngrok terminal.
-
-
Update Vonage Application URLs:
- Go back to your Vonage Application settings page (Applications > Click your application name).
- Find the Messages capability section again.
- In the Inbound URL field, paste your
ngrok
HTTPS URL and append/api/webhooks/inbound
. Example:https://<random-string>.ngrok-free.app/api/webhooks/inbound
- In the Status URL field, you can use the same URL or create a separate handler. For simplicity, let's use the same one for now:
https://<random-string>.ngrok-free.app/api/webhooks/inbound
(You can create a/api/webhooks/status
route later if needed). - Scroll down and click Save changes.
Your Vonage application is now configured to send incoming SMS messages for your linked number to your local Next.js application via ngrok
.
4. Implementing Error Handling and Logging
Our basic webhook handler includes initial logging and error handling, but let's refine it.
- Consistent Error Handling: The
try...catch
block in the API route is the foundation. Ensure any errors encountered during your custom logic (database writes, external API calls) are caught and logged. Always aim to return a500
status if processing fails internally, but only after logging the error details. - Logging:
console.log
is suitable for development. For production, consider structured logging libraries likepino
orwinston
. These enable:- Log Levels: Differentiating between
debug
,info
,warn
,error
. - Structured Formats (JSON): Easier parsing by log aggregation tools (Datadog, Splunk, ELK stack).
- Log Destinations: Sending logs to files, external services, or standard output. In production, especially serverless, configuring transports to send logs to an external aggregation service is crucial, rather than relying solely on console output.
- Log Levels: Differentiating between
- Retry Mechanism Awareness: Vonage will retry sending the webhook if it doesn't receive a
2xx
response (ideally200 OK
) within a certain timeout (usually a few seconds). Your webhook logic should be idempotent if possible – meaning receiving the same message multiple times doesn't cause duplicate actions or errors. Logging themessage_uuid
helps identify duplicate deliveries. If your processing takes time, consider immediately returning200 OK
and then processing the message asynchronously (e.g., using a background job queue like BullMQ or Kue).
Example using Pino (Basic Setup):
-
Install Pino:
npm install pino
-
Update API route (showing App Router example):
app/api/webhooks/inbound/route.js (App Router - Example):
import { NextResponse } from 'next/server'; import pino from 'pino'; // Initialize logger (adjust options for production, e.g., transports) const logger = pino({ level: process.env.LOG_LEVEL || 'info' }); export async function POST(request) { const startTime = Date.now(); let messageUuid = 'unknown'; // Default try { const inboundSms = await request.json(); messageUuid = inboundSms.message_uuid || 'unknown'; // Get UUID early // Use structured logging logger.info({ msg: 'Inbound SMS Received', data: inboundSms }, `Processing message ${messageUuid}`); // --- Add your custom logic here --- // if (someConditionFails) { // throw new Error('Custom logic failed'); // } const duration = Date.now() - startTime; logger.info({ messageUuid, duration }, `Successfully processed message ${messageUuid} in ${duration}ms`); return new NextResponse(null, { status: 200 }); } catch (error) { const duration = Date.now() - startTime; // Log error object with context logger.error({ err: error, messageUuid, duration }, `Error processing message ${messageUuid} in ${duration}ms`); return new NextResponse(JSON.stringify({ error: 'Failed to process webhook' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } } // ... (GET handler remains the same)
5. Creating a Database Schema and Data Layer (Optional)
If you need to store incoming messages or related data, you'll need a database. Prisma is a popular choice with Next.js.
-
Install Prisma:
npm install prisma --save-dev npm install @prisma/client
-
Initialize Prisma:
npx prisma init --datasource-provider postgresql # Or your preferred DB (sqlite, mysql, etc.)
This creates a
prisma
directory with aschema.prisma
file and updates.env
(or.env.local
) withDATABASE_URL
. -
Define Schema: Edit
prisma/schema.prisma
to define a model for SMS messages.prisma/schema.prisma:
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = ""prisma-client-js"" } datasource db { provider = ""postgresql"" // Or your chosen provider url = env(""DATABASE_URL"") } model SmsMessage { id String @id @default(cuid()) messageUuid String @unique @map(""message_uuid"") // Map to Vonage field name fromNumber String @map(""from_number"") toNumber String @map(""to_number"") text String? // Message text can be optional for some types channel String @default(""sms"") receivedAt DateTime @default(now()) @map(""received_at"") processedAt DateTime? @map(""processed_at"") // Timestamp when your logic processed it @@map(""sms_messages"") // Optional: specify table name }
-
Run Migrations: Set up your database connection string in
.env.local
(DATABASE_URL
). Then create and apply the migration.# Create migration files based on schema changes npx prisma migrate dev --name init_sms_message # Apply migrations (usually done by the above command) # npx prisma migrate deploy
-
Use Prisma Client in API Route:
lib/prisma.js (Example Singleton):
import { PrismaClient } from '@prisma/client'; let prisma; if (process.env.NODE_ENV === 'production') { prisma = new PrismaClient(); } else { // Ensure the prisma instance is re-used during hot-reloading in development if (!global.prisma) { global.prisma = new PrismaClient({ // log: ['query', 'info', 'warn', 'error'], // Uncomment for verbose logs }); } prisma = global.prisma; } export default prisma;
app/api/webhooks/inbound/route.js (Update - App Router):
// ... other imports (NextResponse, pino) import prisma from '@/lib/prisma'; // Adjust path as needed import pino from 'pino'; const logger = pino({ level: process.env.LOG_LEVEL || 'info' }); export async function POST(request) { const startTime = Date.now(); let messageUuid = 'unknown'; try { const inboundSms = await request.json(); messageUuid = inboundSms.message_uuid || 'unknown'; logger.info({ msg: 'Inbound SMS Received', data: inboundSms }, `Processing message ${messageUuid}`); // --- Database Interaction --- try { const storedMessage = await prisma.smsMessage.create({ data: { messageUuid: inboundSms.message_uuid, fromNumber: inboundSms.from, toNumber: inboundSms.to, text: inboundSms.text, channel: inboundSms.channel || 'sms', // receivedAt is handled by default processedAt: new Date(), // Mark as processed now }, }); logger.info({ dbId: storedMessage.id, messageUuid }, 'Stored inbound message to DB'); } catch (dbError) { // Handle potential unique constraint violation if message is reprocessed if (dbError.code === 'P2002' && dbError.meta?.target?.includes('message_uuid')) { logger.warn({ messageUuid }, 'Duplicate message received, already processed.'); // Still return 200 OK to Vonage for duplicates if desired return new NextResponse(null, { status: 200 }); } else { logger.error({ err: dbError, messageUuid }, 'Database error storing message'); // Rethrow or handle specifically - might warrant a 500 response throw dbError; // Let outer catch handle sending 500 } } // --- End Database Interaction --- const duration = Date.now() - startTime; logger.info({ messageUuid, duration }, `Successfully processed message ${messageUuid} in ${duration}ms`); return new NextResponse(null, { status: 200 }); } catch (error) { const duration = Date.now() - startTime; logger.error({ err: error, messageUuid, duration }, `Error processing message ${messageUuid} in ${duration}ms`); return new NextResponse(JSON.stringify({ error: 'Failed to process webhook' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } } // ... (GET handler)
6. Adding Security Features
Securing your webhook endpoint is critical.
- Input Validation:
- While the Vonage payload is generally trusted, basic checks ensure required fields exist before processing. Libraries like
zod
orjoi
can validate the incomingreq.body
structure against a predefined schema. - Sanitize any data before storing it in a database or using it in replies to prevent cross-site scripting (XSS) or SQL injection, although Prisma helps significantly against SQL injection.
- While the Vonage payload is generally trusted, basic checks ensure required fields exist before processing. Libraries like
- Verify Vonage Signatures (Highly Recommended for Production):
-
Vonage can sign webhook requests using JWT (JSON Web Tokens) with your Signature secret (found in API Settings). This verifies the request genuinely originated from Vonage.
-
The
@vonage/server-sdk
includes helpers for this. Implementing this adds a robust layer of security. -
Refer to the official Vonage documentation for Secure Webhooks and SDK examples for detailed implementation guidance specific to your framework setup.
-
Conceptual Example Snippet:
// Inside your POST handler (Conceptual - Adapt to your framework/SDK usage) import { Vonage } from '@vonage/server-sdk'; // Or specific signature verification import // ... inside async function POST(request) ... try { const rawBody = await request.text(); // Need raw body for verification const headers = request.headers; // Need request headers const signatureSecret = process.env.VONAGE_SIGNATURE_SECRET; if (!signatureSecret) { logger.warn('VONAGE_SIGNATURE_SECRET not set. Skipping signature verification.'); // Handle accordingly - maybe allow in dev, block in prod? } else { // Conceptual: SDK might offer a function like this // const isValid = Vonage.verifySignature(headers.get('authorization'), rawBody, signatureSecret); // Or you might need to parse JWT manually using `jsonwebtoken` library // Replace with actual SDK/library method call: const isValid = verifyWebhookSignature(headers, rawBody, signatureSecret); // Placeholder for actual verification logic if (!isValid) { logger.warn({ messageUuid }, 'Invalid Vonage signature received.'); return new NextResponse(JSON.stringify({ error: 'Invalid signature' }), { status: 401 }); } logger.info({ messageUuid }, 'Vonage signature verified successfully.'); } // If valid (or skipped), proceed to parse JSON and process const inboundSms = JSON.parse(rawBody); messageUuid = inboundSms.message_uuid || 'unknown'; // ... rest of your processing logic ... } catch (error) { // ... error handling ... } // Placeholder function for actual verification logic function verifyWebhookSignature(headers, rawBody, secret) { // Implement actual verification using Vonage SDK helpers or JWT library // based on Vonage documentation. This involves checking the 'Authorization' header (Bearer token). console.warn(""Signature verification logic not fully implemented in this example.""); return true; // Replace with actual verification result }
Note: Implementing signature verification requires careful handling of the raw request body and headers. Consult the
@vonage/server-sdk
documentation or Vonage developer resources for the precise method. You'll need your Signature Secret from the Vonage Dashboard API Settings.
-
- Rate Limiting:
- Protect your endpoint from abuse or accidental loops.
- Vercel: Provides rate limiting features at the edge.
- Middleware: Use Next.js middleware with libraries like
rate-limiter-flexible
orupstash/ratelimit
to implement custom limits.
- Environment Variables Security:
- Never commit
.env.local
or yourprivate.key
to Git. - Use secrets management solutions provided by your hosting platform (e.g., Vercel Environment Variables, AWS Secrets Manager) for production deployments.
- Never commit
- HTTPS: Always use HTTPS for your webhook URLs (
ngrok
provides this for free; production deployments on platforms like Vercel are HTTPS by default).
7. Handling Special Cases
- Character Encoding: The Vonage Messages API generally handles standard SMS encoding (GSM-7, UCS-2 for Unicode characters like emoji). Ensure your application correctly handles UTF-8 when processing the text.
- Message Concatenation: Longer SMS messages are split into multiple parts but usually reassembled by the receiving device. The Vonage payload might include information about multipart messages (
sms: { num_messages: '...' }
), but typically you receive the full text in thetext
field. - Non-Text Messages: The Messages API supports MMS, WhatsApp, etc. If you enable other channels on your Vonage application, your webhook might receive different payload structures. Adapt your parsing logic accordingly, checking the
channel
field. - Time Zones: Timestamps in the Vonage payload (
timestamp
) are typically in UTC (ISO 8601 format). Store dates in UTC in your database and convert to the user's local time zone only when displaying. - Error Codes from Vonage: When sending messages, handle potential error responses from the Vonage API (e.g., invalid number, insufficient funds). The SDK usually throws errors with details.
8. Implementing Performance Optimizations
For a simple webhook receiver, major optimizations are often unnecessary, but consider:
- Webhook Response Time: Respond with
200 OK
as quickly as possible. Offload time-consuming tasks (database writes, external API calls, complex logic) to background jobs/queues if they risk timing out the webhook request. - Database Queries: Index frequently queried columns (e.g.,
messageUuid
,fromNumber
,receivedAt
) if you store messages. - Caching: If you frequently look up related data to process messages (e.g., user information based on
fromNumber
), cache this data (e.g., using Redis or an in-memory cache) to avoid repeated database hits. - Resource Usage: Monitor CPU and memory usage, especially if running complex logic within the webhook handler. Next.js serverless functions have resource limits.
9. Adding Monitoring, Observability, and Analytics
In production, visibility is key.
- Health Checks: The
GET
handler in our API route provides a basic health check. Monitoring services can ping this endpoint to ensure the webhook is live. - Error Tracking: Integrate services like Sentry or Bugsnag to capture, track, and alert on errors occurring in your API route.
- Logging Aggregation: Use platforms like Datadog, Logz.io, Papertrail, or the ELK stack to collect, search, and analyze logs from your application. Set up alerts for high error rates or specific log patterns.
- Performance Metrics: Hosting platforms like Vercel provide analytics on function duration, invocation counts, and error rates. Dedicated Application Performance Monitoring (APM) tools (Datadog APM, New Relic) offer deeper insights.
- Dashboards: Create dashboards showing key metrics like incoming message volume, webhook response times, error rates, and processing duration.
10. Troubleshooting and Caveats
- Webhook Not Triggering:
- Check ngrok: Is
ngrok
still running? Has the URL expired (free tier URLs are temporary)? - Check Vonage URLs: Are the Inbound/Status URLs in the Vonage Application settings exactly matching your
ngrok
HTTPS URL +/api/webhooks/inbound
? - Check Number Linking: Is the correct Vonage number linked to the correct Vonage Application?
- Check Default API: Is ""Messages API"" set as the default SMS setting in Vonage API Settings?
- Firewall: Is any local or network firewall blocking requests from
ngrok
or Vonage?
- Check ngrok: Is
- Receiving 5xx Errors:
- Check your Next.js application logs (the terminal where
npm run dev
is running, or your production logging service) for detailed error messages immediately preceding the 5xx response. - Did the JSON parsing fail? Is there an error in your custom logic? Database connection issue?
- Check your Next.js application logs (the terminal where
- Vonage Retrying Webhooks:
- Your webhook is likely not returning a
200 OK
status code quickly enough or at all. Check logs for errors or timeouts. Ensureres.status(200).end()
(Pages) orreturn new NextResponse(null, { status: 200 });
(App) is reached successfully.
- Your webhook is likely not returning a
- Environment Variables Not Loaded:
- Ensure the file is named exactly
.env.local
.
- Ensure the file is named exactly