Frequently Asked Questions
Use the Vonage Messages API and Node.js SDK. The sendSms
function in the provided code demonstrates how to initialize the Vonage client and send an SMS message using your API credentials and a Vonage virtual number. It handles asynchronous delivery receipts via webhook, and it includes error handling using try-catch blocks. It includes a clientRef that you can use to correlate messages with your internal system identifiers.
The Vonage Messages API is a multi-channel communication platform that allows you to send SMS, MMS, WhatsApp messages, and more. In this Node.js application, it's used to send SMS messages and configure status webhooks to receive delivery updates.
Vonage uses the Status URL to send real-time delivery receipts (DLRs) to your application as webhooks. This lets your application know the status of sent messages (e.g., delivered, failed) asynchronously, without having to poll the API. You must use a publicly accessible URL like one provided by ngrok during development or your production server's URL for receiving these updates.
Update the Vonage Application's Status URL in the Vonage Dashboard before sending an SMS message, or after restarting your application. This ensures that Vonage knows where to send delivery status updates to your webhook endpoint.
Yes, use ngrok to create a secure tunnel to your locally running Express server. This provides a public HTTPS URL that Vonage can use to reach your webhook endpoint during development. Update your .env
file's BASE_URL and your application's Status URL on Vonage Dashboard with this ngrok URL. Be sure to stop and restart your application after setting this URL.
Set up a webhook endpoint in your Node.js Express application (e.g., /webhooks/status
) and configure this URL as the Status URL in your Vonage application settings. The webhook endpoint receives real-time delivery status updates from the Vonage API, typically a few seconds to a minute after the message has been sent, depending on network availability.
The Vonage Application ID is a unique identifier for your application within the Vonage platform. It's used along with your private key to authenticate requests to the Messages API and link your virtual numbers and webhooks to your application.
The private key is crucial for secure authentication with the Vonage Messages API. It's used to generate JWTs (JSON Web Tokens) for requests, ensuring only authorized applications can send messages and access related data. Never expose your private key publicly and never commit it to version control.
Use ngrok during local development to expose your local webhook server to the public internet so Vonage can send delivery status updates to your webhook endpoint. Ngrok isn't needed in production once deployed to a server with a public URL.
Implement webhook signature verification using verifySignature
method from @vonage/server-sdk
. This ensures requests are from Vonage, preventing spoofing attacks. Configure signature secret in Vonage account settings. This step is critical for production.
The 'delivered' status in a Vonage delivery receipt (DLR) webhook indicates that the SMS message has successfully reached the recipient's mobile handset. Not all carriers or countries reliably report this final status. Sometimes the status might remain 'submitted' or 'accepted', even if delivered. Thorough testing is necessary. If you are getting 'submitted', try texting to a different carrier.
Verify ngrok is running and that the URL matches the Status URL configured in your Vonage account. If the Node.js server is running and no webhooks are received, also confirm no firewall is blocking incoming traffic. Inspect the ngrok inspection interface to see if the requests are being received. If the requests are not being received, check the Vonage Status page for system issues. Also check that the recipient number and virtual number formats are valid. Ensure you are responding to the Vonage webhook with a 2xx status code. Ensure your application has Messages set as the default SMS API.
The clientRef
is an optional user-defined reference string when sending SMS messages using the Vonage API. It lets you associate a unique identifier from your system with each message, allowing for easier tracking, logging, and reconciliation within your own application logic. It appears in status webhooks and logs.
Sinch SMS Delivery Status and Callbacks with Next.js and Supabase
This comprehensive guide demonstrates how to implement SMS delivery tracking in a Next.js application using the Sinch SMS API and Supabase database. Learn how to send SMS messages programmatically, receive real-time delivery status updates through Sinch webhooks, and maintain a complete message audit trail for compliance and analytics.
Business Context: SMS delivery status tracking is essential for time-sensitive communications such as order confirmations requiring proof of delivery, appointment reminders where non-delivery necessitates alternative contact methods, security alerts (2FA codes, fraud warnings) where delivery failures must trigger immediate fallback authentication, and transactional notifications where failed deliveries indicate invalid contact information requiring database updates.
Project Goal: Create a production-ready system that sends SMS messages programmatically, tracks their delivery status through carrier network callbacks, and maintains a persistent audit trail in Supabase for compliance and analytics.
Core Problems Solved:
Technologies Used:
app/api/*/route.ts
convention) for serverless webhook endpoints@sinch/sdk-core
): Official SDK version 1.x supporting Promises and TypeScript definitions (GitHub)@supabase/supabase-js
client librarySystem Architecture:
batch_id
immediately (HTTP 201 Created), then relays the message to the carrier network (SMSC).messages
table) with initial statusQueued
(code 400).Delivered
code 0,Failed
code varies).batch_id
,status
,code
,recipient
, and timestamp.Timing Expectations: Webhook delivery typically occurs within 5-30 seconds for successful deliveries in major markets (US/EU). Carrier delays can extend this to several minutes. Failed deliveries (
Rejected
, status code 402+) are usually reported within seconds. Message expiry (code 406) can take hours if the handset is powered off. Sinch retries failed webhook deliveries with exponential backoff: 5s, 10s, 20s, 40s, 80s, doubling up to 81,920s (~22.75 hours) (Sinch webhook retry documentation).Final Outcome: A Next.js application capable of sending SMS messages, receiving delivery status webhooks, and maintaining a complete message audit trail in Supabase for compliance and analytics.
Prerequisites:
+14155551234
)Estimated Costs (Development):
1. Supabase Database Setup for SMS Delivery Tracking
Configure your Supabase project and create the messages table before writing application code.
1.1 Create Supabase Project
sinch-sms-tracker
), database password, and select region closest to your usershttps://xxxxx.supabase.co
andeyJhbGc...
)1.2 Create Messages Table for SMS Status Tracking
Execute this SQL in the Supabase SQL Editor (Tools > SQL Editor > New query):
Schema Explanation:
batch_id
: Unique identifier returned by Sinch API, used to correlate webhook callbacks with sent messagesstatus
: Human-readable delivery status from Sinch delivery report statuses:Queued
(code 400),Dispatched
(401),Delivered
(0),Rejected
(402),Failed
(varies),Expired
(406),Cancelled
(407),Aborted
(403-405, 408, 410-418),Unknown
(no DLR received)status_code
: Numeric code from Sinch providing detailed failure reasons (see Sinch error codes)2. Sinch Account and API Setup
Obtain credentials and configure webhook URL in the Sinch dashboard.
2.1 Sign Up and Get Credentials
abc123def456...
, ~32 hex characters)Authorization: Bearer YOUR_API_TOKEN
). The Service Plan ID is included in API endpoint paths.https://us.sms.api.sinch.com
https://eu.sms.api.sinch.com
https://ca.sms.api.sinch.com
https://au.sms.api.sinch.com
https://br.sms.api.sinch.com
2.2 Purchase Virtual Number
+12065551234
)Number Selection Guidance: For US/Canada, local numbers provide best deliverability and cost-effectiveness. Toll-free numbers are subject to additional carrier filtering and may require 10DLC registration for business messaging. International deployments should verify local regulations regarding sender ID types.
2.3 Configure Webhook URL for Delivery Reports (After Deployment)
Important: Complete this step after deploying your Next.js application (Section 5) to obtain the public webhook URL.
https://abc123.ngrok.io/api/webhooks/sinch/delivery
https://your-app.vercel.app/api/webhooks/sinch/delivery
https://user:pass@your-domain.com/api/webhooks/sinch/delivery
). Secure but credentials visible in logs.X-Sinch-Signature: secret_value
) for additional validation.Security Note: Webhook URLs are called by Sinch servers, not your users. Implement request validation (see Section 6.2) to prevent unauthorized POST requests from spoofed sources.
3. Next.js Project Setup
Initialize a new Next.js 14+ project with TypeScript and install dependencies.
3.1 Create Next.js Application
Expected output shows
app/
directory withlayout.tsx
,page.tsx
, andglobals.css
.3.2 Install Dependencies
Version Specifications:
@sinch/sdk-core@^1.1.0
: Stable release with SMS batches, delivery reports, and webhook helpers (changelog)@supabase/supabase-js@^2.39.0
: Latest stable with improved TypeScript definitions and connection pooling3.3 Configure Environment Variables
Create
.env.local
in project root (automatically excluded from Git in Next.js):E.164 Format Explanation: International standard for phone numbers:
+[country_code][subscriber_number]
. Examples: US+14155551234
(country code 1), UK+447700123456
(country code 44), Singapore+6598765432
(country code 65). Omit leading zeros, spaces, or special characters. ITU-T E.164 standard.Security Best Practices:
.env.local
to version control3.4 Project Structure
4. Implementation: Sending SMS and Handling Delivery Webhooks
4.1 Initialize Sinch Client (
lib/sinch.ts
)Code Explanation:
SinchClient
constructor acceptsservicePlanId
,apiToken
, andregion
for authentication and routingdelivery_report: 'per_recipient'
enables webhook callbacks for each status change (alternative:'per_recipient_final'
for final status only, or'summary'
for batch-level report without webhooks)type: 'mt_text'
specifies standard SMS (alternatives:'mt_binary'
for binary data,'mt_media'
for MMS)4.2 Initialize Supabase Client (
lib/supabase.ts
)Client Types:
supabase
(anon key): Respects Row Level Security policies, safe for client-side usesupabaseAdmin
(service role key): Bypasses RLS, full database access, use only server-side for administrative operations like webhook processing4.3 TypeScript Interfaces (
types/sinch.ts
)4.4 API Route: Send SMS (
app/api/sms/send/route.ts
)Key Implementation Details:
Queued
(code 400) reflects message acceptance by Sinch, not carrier delivery4.5 API Route: Webhook Handler for SMS Delivery Status (
app/api/webhooks/sinch/delivery/route.ts
)Webhook Status Codes Summary:
Delivered
Queued
Dispatched
Aborted
Aborted
Aborted
Expired
Cancelled
Aborted
Idempotency Considerations: Sinch may retry webhooks if they don't receive a 2xx response within timeout (~30 seconds). Use
batch_id
as idempotency key to prevent duplicate processing. The SupabaseUPDATE
operation is idempotent by design (updating same record multiple times with same values has no adverse effect). For critical operations (e.g., triggering refunds, sending emails), implement explicit idempotency checks (e.g., check ifstatus
already equals incoming status before processing).5. Testing SMS Delivery Status Webhooks
5.1 Local Development with ngrok
Start Next.js Development Server:
Expose Localhost with ngrok:
Open a new terminal and run:
Copy the HTTPS forwarding URL (e.g.,
https://abc123.ngrok.io
)Configure Sinch Webhook URL:
https://abc123.ngrok.io/api/webhooks/sinch/delivery
Test SMS Sending:
Verify Results:
Terminal: Check
npm run dev
logs for "📥 Delivery report webhook received"Phone: Receive SMS within 5-30 seconds
Supabase: Query
messages
table in SQL Editor:ngrok Dashboard: Inspect webhook requests at
http://127.0.0.1:4040
(shows full request/response)Expected Webhook Payload Example:
5.2 Deployment to Vercel
Initialize Git Repository:
Push to GitHub/GitLab:
Create a repository on GitHub and push:
Deploy to Vercel:
.env.local
):SINCH_SERVICE_PLAN_ID
SINCH_API_TOKEN
SINCH_REGION
SINCH_FROM_NUMBER
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY
(optional)WEBHOOK_SECRET
(optional)Update Sinch Webhook URL:
https://your-app.vercel.app
)https://your-app.vercel.app/api/webhooks/sinch/delivery
Test Production Deployment:
6. Security and Production Considerations
6.1 Environment Variable Security
.env.local
to Git (Next.js automatically excludes it via.gitignore
)6.2 Webhook Signature Verification (CRITICAL for Production)
Without signature verification, any attacker can send fake delivery reports to your webhook endpoint. Implement one of these methods:
Option A: HMAC Signature Verification (Recommended)
Contact your Sinch account manager to enable HMAC signing. Sinch will include a signature in the webhook request that you can verify:
Option B: Custom Header Authentication
Request Sinch to inject a custom header (e.g.,
X-Webhook-Token
) and validate it:Option C: IP Allowlisting
Restrict webhook endpoint to Sinch IP ranges (contact Sinch support for current IPs). Configure in
next.config.js
with middleware or use Vercel Firewall.6.3 Rate Limiting
Protect your webhook endpoint from abuse:
6.4 Error Handling and Monitoring
Logging:
Error Tracking:
Integrate Sentry for exception tracking:
Monitoring:
6.5 Retry Logic for Failed SMS
Implement retry with exponential backoff for transient Sinch API errors (network timeouts, 5xx responses):
7. Troubleshooting Common Issues
7.1 SMS Not Sending
Symptom: API returns 201 but no SMS received.
Debugging Steps:
+14155551234
). No spaces, dashes, or parentheses.SINCH_FROM_NUMBER
matches a purchased number in your account.7.2 Webhook Not Received
Symptom: SMS delivered to phone but database status remains "Queued".
Debugging Steps:
Verify Webhook URL Configuration: Check Sinch Dashboard > SMS > APIs > Callback URLs. Must be publicly accessible HTTPS URL.
Test Webhook Endpoint Manually:
Should return 204 No Content.
Check ngrok Connection: If using ngrok, ensure tunnel is active (
ngrok http 3000
running).Review Webhook Logs: Check Next.js console (
npm run dev
) for incoming requests and errors.Inspect Sinch Retry Attempts: Sinch retries webhooks on 5xx/timeout. Check if retries are accumulating (indicates endpoint issues).
Verify Delivery Report Setting: When sending SMS, ensure
delivery_report: 'per_recipient'
is set (see Section 4.1).7.3 Database Write Failures
Symptom: Webhook received but Supabase update fails.
Common Causes:
RLS Policy Mismatch: If using
supabase
client (anon key) in webhook handler, RLS may block updates. UsesupabaseAdmin
instead.Missing batch_id: If message was sent outside this application,
batch_id
won't exist in database. Add defensive check:Network Timeout: Supabase queries exceeding 30s cause Sinch to retry. Optimize queries with indexes (see Section 1.2).
7.4 Rate Limit Errors
Symptom: Sinch API returns 429 Too Many Requests.
Solution: Sinch enforces rate limits (default 600 requests/minute for SMS API). Implement client-side throttling:
Contact Sinch support to increase rate limits for high-volume use cases.
8. Advanced Features
8.1 Querying Delivery Reports via API
Fetch delivery reports programmatically without webhooks:
Use case: Reconcile missed webhooks or generate reports for specific batches.
8.2 Scheduled Messages with Supabase Edge Functions
Implement send-at-time functionality using Supabase Edge Functions triggered by cron:
8.3 Two-Way Messaging (Inbound SMS)
Handle incoming SMS replies by configuring inbound webhook in Sinch Dashboard. Learn more about implementing two-way SMS messaging with Sinch:
Configure in Sinch Dashboard: SMS > APIs > Inbound callback URL:
https://your-app.vercel.app/api/webhooks/sinch/inbound
8.4 Message Templates and Parameterization
Reduce code duplication with parameterized templates:
9. Conclusion
You have successfully built a production-ready Next.js application that:
Key Learnings:
batch_id
are essential for fast webhook processing at scaleProduction Checklist:
Next Steps:
type: 'mt_media'
)Related Resources: