Frequently Asked Questions
Use Next.js API routes to create a webhook endpoint. This endpoint will receive incoming SMS messages forwarded from your MessageBird virtual number via the MessageBird Flow Builder. This allows your Next.js application to process incoming texts and react accordingly.
MessageBird Flow Builder is used to route incoming SMS messages to your Next.js webhook. You configure the Flow Builder to trigger the webhook URL with the message data whenever an SMS is received on your designated MessageBird virtual number.
Next.js API routes provide a serverless function environment ideal for handling webhooks. They offer an easy way to create server-side logic without managing a separate backend server, simplifying development and deployment, especially on Vercel.
ngrok is essential during local development. It creates a public tunnel to your local server so MessageBird can reach your webhook endpoint while you are testing. For production, deploy to Vercel and use the Vercel app URL.
First, create a Next.js API route. Then, in your MessageBird Flow Builder, add an HTTP endpoint step. Paste your ngrok HTTPS URL (for local development) or your Vercel app URL (for production) along with the API endpoint path and your secret as a query parameter.
Use ngrok to create a public URL for your local development server. Configure MessageBird Flow Builder with this URL, then send an SMS to your MessageBird number. Observe logs in your Next.js terminal for successful webhook delivery. Also, you can simulate MessageBird POST requests with curl. Check the logs for validation success and received payloads.
This secret, stored in your .env.local file, adds security to your webhook. By including it as a query parameter in your webhook URL, you ensure only requests with the correct secret, presumably from MessageBird, are processed by your application.
The webhook sends data as application/x-www-form-urlencoded. Key fields include 'originator' (sender's number), 'payload' (the message content), 'recipient' (your virtual number), 'messageId', and 'createdDatetime'. Logging the full received data is recommended.
Use a strong, randomly generated secret as a query parameter in the URL. Verify this secret on your Next.js API route to ensure only MessageBird is triggering the webhook. Also, implement input sanitization, rate limiting, and possibly timestamp checks.
MessageBird typically handles long SMS (concatenated messages) automatically. You should receive the entire reassembled message in the 'payload' field of your webhook. Testing long messages is recommended.
Avoid using SQLite in production on serverless platforms like Vercel due to ephemeral file systems. Use a managed database service like Vercel Postgres, PlanetScale, or Supabase, especially if you need persistent data storage. For local development and testing, SQLite is convenient.
Prisma is an Object-Relational Mapper (ORM) that simplifies database interactions. It helps define your database schema, perform migrations, and access data easily within your API route handler. It's particularly useful for modeling and handling database relations for complex systems.
The guide recommends Prisma with SQLite for development and testing. For production, use a persistent, managed database solution. In your webhook handler, after validating the request, use Prisma Client to create a new database entry with the message details.
Implement thorough error handling and logging in your webhook handler. Catch potential errors, log them, and return appropriate status codes. Consider using a structured logging library like 'pino'. Critically, try to always return 200 OK to MessageBird quickly, even on errors, unless you specifically want MessageBird to retry.
This guide provides a complete walkthrough for building a Next.js application capable of receiving inbound SMS messages sent to a MessageBird virtual number. We will create a webhook endpoint using Next.js API routes, configure MessageBird's Flow Builder to forward incoming messages to this endpoint, and deploy the application to Vercel.
This enables developers to build applications that react to incoming SMS, such as automated responders, two-way chat systems, notification triggers, or data collection services via text message.
Project Goals:
ngrok
.Technologies Used:
System Architecture:
(Ensure your publishing platform supports Mermaid diagram rendering)
Prerequisites:
ngrok
installed globally or available vianpx
for local testing.Final Outcome:
A deployed Next.js application with a publicly accessible API endpoint that logs incoming SMS messages sent to your configured MessageBird number. The endpoint will be secured with a basic shared secret.
1. Setting up the Project
We'll start by creating a new Next.js application.
1.1 Create Next.js App:
Open your terminal and run the following command, replacing
messagebird-inbound-app
with your desired project name:Follow the prompts. We recommend selecting the following options for this guide:
src/
directory? Yes1.2 Navigate to Project Directory:
1.3 Project Structure:
Your basic structure will look something like this (using
src/
and App Router):1.4 Environment Variables:
Create a file named
.env.local
in the root of your project. This file will store sensitive information and configuration details. Do not commit this file to version control. Add it to your.gitignore
if it's not already there (it should be by default withcreate-next-app
).Generate a strong, random secret string. You can use a password generator or run this command in your terminal:
Copy the generated secret and add it to your
.env.local
file:Purpose of Configuration:
create-next-app
: Scaffolds a modern Next.js application with best practices..env.local
: Securely stores environment-specific variables like API keys or secrets, keeping them out of the codebase.MESSAGEBIRD_WEBHOOK_SECRET
is used to ensure that incoming webhook requests genuinely originate from MessageBird (or at least know the secret).2. Implementing the Webhook Handler
We'll create an API route within our Next.js application to act as the webhook endpoint that MessageBird will call.
2.1 Create the API Route File:
Inside the
src/app/api/
directory, create a new folder namedmessagebird-webhook
. Inside this folder, create a file namedroute.js
.Directory Structure:
src/app/api/messagebird-webhook/route.js
2.2 Implement the Handler:
Paste the following code into
src/app/api/messagebird-webhook/route.js
:Code Explanation:
NextResponse
: Used for sending responses from API routes.POST
Function: This is the primary handler for incoming webhook requests from MessageBird, which uses thePOST
HTTP method for SMS webhooks.MESSAGEBIRD_WEBHOOK_SECRET
is loaded correctly. If not, it logs an error but returns a200 OK
to prevent MessageBird from endlessly retrying due to a server configuration issue. You must fix the environment variable setup if this occurs.secret
query parameter from the incoming request URL (e.g.,https://yourapp.com/api/messagebird-webhook?secret=your_secret
).providedSecret
with the one stored in your environment variables (process.env.MESSAGEBIRD_WEBHOOK_SECRET
).403 Forbidden
status, rejecting the request.application/x-www-form-urlencoded
.request.formData()
parses this into aFormData
object, which we convert to a plain JavaScript object (messageData
).originator
,payload
,recipient
, etc.).originator
,payload
). If missing, it returns a400 Bad Request
.200 OK
response usingNextResponse
. MessageBird expects this to confirm successful receipt. The response body is ignored by MessageBird for SMS webhooks.try...catch
block catches any unexpected errors during processing, logs them, and returns a500 Internal Server Error
.GET
Function (Optional): A simple handler forGET
requests is included. This isn't used by MessageBird for SMS webhooks but can be useful for manually checking if the endpoint is reachable or for setting up simple external health checks.3. API Endpoint Details (Webhook)
While not a traditional user-facing API, the webhook endpoint acts as an API for MessageBird.
/api/messagebird-webhook
POST
?secret=YOUR_SECRET
).application/x-www-form-urlencoded
originator
: String (Sender's phone number in international format)payload
: String (The SMS message content)recipient
: String (Your MessageBird virtual number)messageId
: String (Unique MessageBird ID)createdDatetime
: String (ISO 8601 timestamp)200 OK
(Empty body or simple text confirmation)403 Forbidden
: Invalid or missingsecret
query parameter.400 Bad Request
: Missing required fields (originator
orpayload
).500 Internal Server Error
: Unexpected server-side error during processing.200 OK
(with server-side error logged): IfMESSAGEBIRD_WEBHOOK_SECRET
is not configured.Testing with
curl
(Simulating MessageBird):You'll need your local server running (
npm run dev
) andngrok
exposing it (see Section 4). ReplaceYOUR_NGROK_URL
andYOUR_SECRET
accordingly. Use a realistic past or generic date forcreatedDatetime
.Note on
curl
quoting: The command above uses double quotes for data fields and the URL. This generally works in bash/zsh. If you encounter issues in other shells or if your secret contains special shell characters, try putting single quotes around the entire URL:'YOUR_NGROK_URL/api/messagebird-webhook?secret=YOUR_SECRET'
.Check your terminal running
npm run dev
for the log output from theroute.js
handler.4. Integrating with MessageBird
Now, let's configure MessageBird to send incoming SMS messages to our Next.js application.
4.1 Local Development Setup (ngrok):
MessageBird needs a publicly accessible URL to send webhooks to. During development, your local machine isn't typically accessible. We use
ngrok
to create a secure tunnel.Start your Next.js dev server:
It usually runs on
http://localhost:3000
.Start ngrok: Open another terminal window and run:
Copy the ngrok URL:
ngrok
will display forwarding URLs. Copy thehttps
URL (e.g.,https://random-subdomain.ngrok-free.app
). This is your temporary public URL.4.2 Configure MessageBird Flow Builder:
https
URL (from step 4.1.3) and append the API route path and the secret query parameter:https://your-random-subdomain.ngrok-free.app/api/messagebird-webhook?secret=your_generated_secret_string_here
(Replace the URL and secret with your actual values from.env.local
)Diagram of Flow Builder Setup:
(Ensure your publishing platform supports Mermaid diagram rendering)
Explanation of Environment Variables:
MESSAGEBIRD_WEBHOOK_SECRET
(used in.env.local
and later in Vercel): This secret string is added as a query parameter to the webhook URL configured in Flow Builder. The Next.js API route verifies this secret upon receiving a request, ensuring that only requests knowing the secret (presumably only MessageBird via your Flow Builder config) are processed.Testing Local Integration:
Send an SMS message from your phone to the MessageBird virtual number you configured in Flow Builder.
npm run dev
. You should see theconsole.log
output from yourroute.js
file, including the ""Webhook secret validated successfully"" message and the ""Received MessageBird Payload"".ngrok
. You should see an incomingPOST
request to/api/messagebird-webhook
with a200 OK
response status.If it works, your local setup is correctly receiving messages!
5. Implementing Error Handling and Logging
Our
route.js
already includes basic error handling and logging:MESSAGEBIRD_WEBHOOK_SECRET
. Logs error server-side, returns200 OK
to MessageBird.403 Forbidden
.400 Bad Request
.try...catch
block captures unexpected errors during processing. Logs error, returns500 Internal Server Error
.console.log
,console.warn
, andconsole.error
for different levels of information. The entire payload is logged for debugging.Improvements for Production:
pino
for structured JSON logs, which are easier to parse and analyze in log management systems.route.js
to usepino
.async-retry
) within your handler after sending the200 OK
to MessageBird, or preferably by pushing the message to a queue (like Redis, RabbitMQ, or AWS SQS) for background processing with retries. Do not delay the200 OK
response to MessageBird.Testing Error Scenarios:
curl
request (Section 3) with the wrong or nosecret
parameter. Expect a403 Forbidden
.curl
request without theoriginator
orpayload
form data fields. Expect a400 Bad Request
.throw new Error('Simulated processing error');
inside thetry
block before the finalreturn
. Send a valid request. Expect a500 Internal Server Error
response and an error log in the console.6. Creating a Database Schema and Data Layer (Optional)
For most real-world applications, you'll want to store incoming messages. Let's add basic persistence using Prisma and SQLite.
Important Note on SQLite: SQLite is excellent for local development and simple use cases due to its file-based nature and ease of setup. However, it is generally NOT recommended for production deployments on serverless platforms like Vercel. Vercel's filesystem is ephemeral, meaning the SQLite file can be lost during deployments or scaling events. For production, use a managed database service like Vercel Postgres, Neon, PlanetScale, Supabase, etc., and configure Prisma accordingly. The following steps use SQLite for demonstration purposes.
6.1 Install Prisma:
6.2 Initialize Prisma:
This creates a
prisma
directory with aschema.prisma
file and updates.env.local
with aDATABASE_URL
.6.3 Define Schema:
Open
prisma/schema.prisma
and define a model for incoming messages:6.4 Create Initial Migration:
This creates the SQLite database file (e.g.,
prisma/dev.db
based on yourDATABASE_URL
) and theIncomingMessage
table. Make sureprisma/dev.db
is added to your.gitignore
.6.5 Implement Data Layer:
Create a utility function to instantiate and share the Prisma client instance.
6.6 Update Webhook Handler to Save Message:
Modify
src/app/api/messagebird-webhook/route.js
:Key Changes:
@/lib/prisma
).try...catch
block specifically for the database operation (prisma.incomingMessage.create
).prisma.incomingMessage.create
to save the relevant data extracted frommessageData
.P2002
) specifically on themessageBirdId
field. If it's a duplicate, log a warning and return200 OK
.messageId
to the required fields check.200 OK
response is now sent after the database operation attempt (unless it was a handled duplicate).Retest by sending an SMS. You should see the ""saved to DB"" log message in your development console. You can inspect the
prisma/dev.db
file (using tools like DB Browser for SQLite) to see the saved record.7. Adding Security Features
Beyond the shared secret, consider these:
payload
elsewhere (e.g., displaying in a UI), sanitize it to prevent Cross-Site Scripting (XSS) attacks (e.g., using libraries likedompurify
).rate-limiter-flexible
or platform features (like Vercel's built-in IP rate limiting) can be used.createdDatetime
from MessageBird against the current server time. Reject requests that are too old (e.g., > 5 minutes) to mitigate simple replay attacks, but be cautious about potential clock skew between MessageBird's servers and yours.ngrok
tunnel useshttps
and your production webhook URL useshttps
. Never use plainhttp
for webhooks handling sensitive data or secrets.Testing Security:
curl
or a similar tool to send requests without thesecret
, with an incorrectsecret
, or with malformed data to confirm your validation logic returns403 Forbidden
or400 Bad Request
appropriately.8. Handling Special Cases
GSM-7
orUCS-2
). MessageBird typically handles the decoding and provides the messagepayload
as a standard UTF-8 string in the webhook. Be aware of this if your application needs to interact with external systems expecting specific SMS encodings.payload
you receive should contain the full message content. It's good practice to test with messages longer than 160 characters (GSM-7) or 70 characters (UCS-2) to confirm this behavior.createdDatetime
from MessageBird or yourreceivedAt
timestamp) to order messages during processing or retrieval.originator
,recipient
) are generally provided in the standard E.164 format (e.g.,+14155552671
). Ensure your system stores and processes these numbers correctly. If your application logic depends on the content (payload
) of the message (e.g., language detection, keyword analysis), consider internationalization and localization requirements.9. Implementing Performance Optimizations
For a simple webhook receiver primarily logging or storing data, performance is usually manageable unless facing very high message volumes. Key considerations include:
200 OK
to MessageBird as quickly as possible (ideally within 1-2 seconds). Defer any potentially slow or unreliable operations (like complex database queries, external API calls, sending replies) until after the response has been sent.console.time
/console.timeEnd
or dedicated profiling tools) to identify bottlenecks within the handler if performance issues arise. Optimize algorithms and data structures.Testing Performance:
k6
orartillery.io
to simulate high volumes of concurrent webhook requests to your deployed endpoint (not justngrok
).