Frequently Asked Questions
Set up Sinch SMS delivery reports by creating a webhook endpoint in your application. This endpoint receives real-time status updates from Sinch about your sent messages. The setup involves configuring your Sinch account, setting up a web server to handle incoming requests, and implementing logic to process the delivery reports. Use a tool like ngrok to expose your local development server for testing callbacks.
A Sinch SMS Delivery Report (DLR) is a real-time notification from Sinch that provides the status of your sent SMS messages. These reports are delivered to a webhook URL you specify, allowing your application to track message delivery status like "Delivered", "Failed", or "Pending". DLRs are crucial for applications needing reliable communication and status monitoring.
Fastify is a high-performance web framework for Node.js, chosen for its speed, extensibility, and developer-friendly features. Its efficiency makes it ideal for handling real-time callbacks like Sinch DLRs, ensuring quick responses and minimal overhead. Fastify's plugin system also simplifies adding features like authentication and rate limiting.
Test Sinch DLR webhooks locally using ngrok, which creates a public URL that tunnels requests to your local server. After starting ngrok, update your .env file's CALLBACK_BASE_URL with your ngrok HTTPS URL. Then, start your Fastify server and send a test SMS using the provided /send-test-sms endpoint. Observe your server logs and the ngrok interface to verify successful callback processing.
Prisma is a next-generation ORM (Object-Relational Mapper) used for database schema management and interactions. In this Sinch DLR setup, Prisma simplifies database operations by defining a schema for storing message statuses, generating migrations, and providing a clean API for querying and updating data. It streamlines database interaction in your Node.js application.
The provided code includes a /send-test-sms route in server.js. Access it via a GET request with query parameters for 'to', 'from' (optional), and 'body' (optional). It uses your Sinch API credentials and ngrok URL to send an SMS configured to trigger delivery report callbacks to your webhook endpoint. Remember to update your environment variables before running.
Use 'per_recipient' delivery reports when you require real-time status updates for each individual recipient of your SMS messages. This setting, configured in the Sinch API request payload, ensures that your webhook receives a callback for every status change for each recipient. This is ideal for applications requiring granular tracking of delivery outcomes.
The database schema, defined in prisma/schema.prisma, includes a MessageStatus model to store the status updates. It stores batchId, recipient, status, statusCode, timestamps, and an optional clientReference. A unique constraint is defined on batchId and recipient to enable efficient upsert operations. This constraint is important to ensure that each message recipient has only one record in the database.
Yes, you can use SQLite for the Sinch DLR project, especially for simplified local development. During Prisma initialization, choose SQLite as the datasource provider and update the DATABASE_URL in your .env file accordingly. The code adapts to either PostgreSQL (used in the example) or SQLite based on this environment variable.
Secure your Sinch DLR webhook ideally with signature verification, if available, by checking a Sinch-generated signature against your shared secret. If not available, use Basic Authentication by configuring credentials in your Sinch Dashboard and adding authentication checks in your webhook endpoint. Always use HTTPS and consider rate limiting to prevent abuse.
Sinch requires an immediate 200 OK response to acknowledge receipt of the delivery report callback. This confirms to Sinch that your endpoint received the data. Processing the DLR payload should happen asynchronously after sending the 200 OK to prevent Sinch from retrying the callback unnecessarily if processing takes time.
Handle Sinch DLR errors by implementing robust error handling and logging within your processDeliveryReport function. Log errors during validation, database interactions, or timestamp parsing. For critical failures, consider a dead-letter queue (DLQ) to store failed payloads for later inspection or retry. Monitor your logs and endpoint health.
ngrok creates a secure tunnel from a public URL to your local development server. It's essential for testing Sinch webhooks locally, as it allows Sinch to send callbacks to your server even though it's not publicly accessible. Update your .env file with the ngrok HTTPS URL and restart your server after starting ngrok.
Install the required dependencies for the Sinch DLR project using npm or yarn. The main dependencies include fastify, dotenv, axios, and @prisma/client. For Prisma setup, install the Prisma CLI as a development dependency using npm install -D prisma. Ensure your .env file is configured with your database and Sinch credentials before running.
This guide provides a step-by-step walkthrough for building a robust system to handle Sinch SMS delivery report (DLR) callbacks using Fastify, Node.js, and Prisma. You'll learn how to receive, process, and store real-time updates on the status of messages you send via the Sinch SMS API.
By the end of this tutorial, you will have a functional webhook endpoint capable of receiving DLRs from Sinch, processing them, and persisting the status updates to a database. This enables real-time tracking of message delivery, crucial for applications requiring reliable communication and status monitoring.
Project Goals:
Technologies Used:
.env
file.System Architecture:
Prerequisites:
ngrok
installed for local testing (ngrok Installation Guide).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 the project.
Initialize Node.js Project: Initialize the project using npm (or yarn).
This creates a
package.json
file.Install Dependencies: Install Fastify, dotenv (for environment variables), axios (for sending SMS), and Prisma (for database interaction).
Install Development Dependencies: Install Prisma CLI as a development dependency.
Initialize Prisma: Set up Prisma in your project. We'll use PostgreSQL as an example, but you can change the provider if needed (e.g.,
sqlite
).This creates:
prisma
directory with aschema.prisma
file for defining your database schema..env
file (if it doesn't exist) for environment variables, including theDATABASE_URL
.Configure
.gitignore
: Create a.gitignore
file in the root of your project to avoid committing sensitive information and unnecessary files.Project Structure: Your basic project structure should look like this:
2. Environment Configuration
Securely manage your API keys and other configuration settings using environment variables.
Edit
.env
File: Open the.env
file created byprisma init
(or create it if it doesn't exist) and add your Sinch credentials and other configurations. You MUST replace the placeholder values below with your actual credentials and URLs.DATABASE_URL
: Crucial. Replace the example string with your actual database connection string. If using SQLite locally_ ensure the path is correct (e.g._file:./prisma/dev.db
). Make sure the database exists if using PostgreSQL.SINCH_SERVICE_PLAN_ID
: Required. Find this on your Sinch Dashboard under API Credentials.SINCH_API_TOKEN
: Required. Also found on your Sinch Dashboard. Keep this secret!PORT
: The port your Fastify server will listen on (default is 3000).CALLBACK_BASE_URL
: Required. This is the publicly accessible base URL where Sinch will send callbacks. When testing locally with ngrok_ you'll replace the placeholder with your ngrok forwarding URL (thehttps://...
one). In production_ this will be your server's public domain/IP.WEBHOOK_USER
/WEBHOOK_PASSWORD
: (Optional) Only needed if you configure Basic Authentication for your callback URL in the Sinch Dashboard and implement the check in Section 7.Load Environment Variables: We'll load these variables into our application using
dotenv
in theserver.js
file. Ensurerequire('dotenv').config();
is called early in your application startup.3. Creating the Database Schema
Define the database schema using Prisma to store message status information.
Define the Schema (
prisma/schema.prisma
): Openprisma/schema.prisma
and define a model to store the delivery status updates. We add a unique constraint onbatchId
andrecipient
to facilitate theupsert
operation correctly.id
_ automatically generated by Prisma.@@unique([batchId_ recipient])
constraint is key. It ensures that for any given message batch_ each recipient can only have one status record. This constraint is used by theupsert
operation in the code.receivedAt
defaults to the time the record is created in our database.updatedAt
automatically tracks the last modification time.Apply Migrations: Generate the SQL migration files based on your schema and apply them to your database.
This command will:
DATABASE_URL
.schema.prisma
(including creating theMessageStatus
table and the unique constraint).4. Building the Callback Endpoint
Now_ let's create the Fastify server and the endpoint to receive callbacks.
Create
server.js
: Create a file namedserver.js
in the root of your project.Implement the Server and Endpoint: Add the following code to
server.js
:Explanation:
dotenv
loads variables from.env
. Crucially, it's called first./webhooks/sinch/dlr
Route:webhookHandler
.200 OK
immediately. This is vital for Sinch.processDeliveryReport
asynchronously after responding. Errors during processing are logged but don't cause Sinch retries (unless you implement a Dead Letter Queue).processDeliveryReport
Function:type
.batch_id
,recipient
,status
,code
,at
).prisma.messageStatus.upsert
with thebatchId_recipient
unique constraint defined in the schema. This correctly finds an existing record to update or creates a new one.id
field is not set in thecreate
block; Prisma handles the CUID generation./health
Route: Standard health check./send-test-sms
Route: (Included from Section 5 for completeness) Allows triggering an SMS for testing.PORT
and0.0.0.0
. Logs the expected callback URL.SIGINT
(Ctrl+C) andSIGTERM
to close the server and database connection cleanly.5. Sending an SMS with Callback Configuration
To test the webhook, you need to send an SMS using the Sinch API and tell Sinch where to send the delivery report callback. The code for the
/send-test-sms
route is included in theserver.js
example in Section 4.Explanation of the Test Route:
to
(required, E.164 format),from
(optional), andbody
(optional) as query parameters.SINCH_SERVICE_PLAN_ID
,SINCH_API_TOKEN
,CALLBACK_BASE_URL
).us
is correct for your account).to
: Array containing the recipient number in E.164 format.delivery_report: 'per_recipient'
: Tells Sinch to send a callback for each recipient's status changes. Study Sinch docs if you needsummary
orfinal
reports, as the payload structure differs.callback_url
: The full, public URL of your webhook endpoint (/webhooks/sinch/dlr
appended toCALLBACK_BASE_URL
).axios
to make the POST request with the Bearer token.batch_id
.6. Running and Testing Locally with ngrok
To allow Sinch's servers (on the public internet) to reach your local Fastify application during development, use
ngrok
.Start Your Database: Ensure your PostgreSQL server is running or your SQLite file path is correct.
Start ngrok: Open a new, separate terminal window and run ngrok, telling it to forward HTTP traffic to the port your Fastify server runs on (default 3000).
Get ngrok Forwarding URL: ngrok will display output like this:
Copy the HTTPS forwarding URL (e.g.,
https://xxxxxxxx.ngrok-free.app
). This URL is now publicly accessible as long as ngrok is running. Note: Free ngrok URLs are temporary and change each time you restart ngrok.Update
.env
: Open your.env
file and replace the placeholderCALLBACK_BASE_URL
with the actual HTTPS ngrok URL you just copied.IMPORTANT: You must restart your Fastify server (
node server.js
) after changing the.env
file for the changes to take effect.Start Fastify Server: In your original terminal window (in the project directory), start the server.
Check the logs. It should confirm it's listening and state the full callback URL it expects Sinch to use (based on your
.env
setting). IfCALLBACK_BASE_URL
was missing or incorrect, you'll see a warning.Trigger Test SMS: Open your web browser or use a tool like
curl
to hit your server's test endpoint. Provide a valid phone number you can check (use your own mobile, in E.164 format like+15551234567
).Observe Logs and Callbacks:
/send-test-sms
./webhooks/sinch/dlr
as Sinch sends status updates (e.g.,Dispatched
,Delivered
,Failed
).processDeliveryReport
showing status processing and database interaction (Successfully saved/updated status...
).http://127.0.0.1:4040
in your browser. This is ngrok's local inspection interface. It shows all requests coming through the tunnel, which is very useful for debugging if your Fastify endpoint isn't receiving callbacks as expected (you can inspect headers, body, timing, etc.).psql
, pgAdmin,sqlite3
, or Prisma Studio:npx prisma studio
) and check theMessageStatus
table. You should see records being created or updated corresponding to the DLRs received for your test message.7. Security Considerations
Securing your webhook endpoint is crucial in production.
Webhook Signature Verification (Ideal but Check Availability): The most secure method is verifying a signature sent by Sinch (usually HMAC), calculated using a shared secret. Currently, standard HMAC signature verification does not seem to be offered by Sinch for SMS DLR callbacks. Always consult the latest official Sinch documentation for confirmation and potential updates on security features. If signature verification becomes available, prioritize implementing it.
Basic Authentication (Alternative): If signature verification isn't available, you can use Basic Authentication as a layer of security. Configure a username and password in the Sinch Dashboard for your callback URL.
@fastify/basic-auth
plugin.preHandler
hook inserver.js
to check credentials before your webhook logic runs. Add this code before thefastify.post('/webhooks/sinch/dlr', ...)
line and before thestart()
call.fastify.post('/webhooks/sinch/dlr', webhookHandler);
line defined earlier is either removed or placed within theelse
block here, so the route isn't registered twice. The code above assumes thefastify.route({...})
call replaces any previous definition for the same method/URL. Check Fastify documentation for specifics if needed. The key is that the route is registered either with thepreHandler
(if auth is enabled) or without it (if auth is disabled).HTTPS: Always use HTTPS for your callback URL (
CALLBACK_BASE_URL
).ngrok
provides this automatically for local testing. In production, ensure your server is behind a reverse proxy (like Nginx or Caddy) configured for SSL/TLS termination.Rate Limiting: Implement rate limiting (e.g., using
@fastify/rate-limit
) on the webhook endpoint to prevent abuse or accidental overload.Input Validation: The current code performs basic validation. Enhance it based on the exact Sinch DLR payload specification to be more robust against unexpected or malformed data.
Error Handling & Monitoring: Implement robust error handling. Consider a dead-letter queue (DLQ) mechanism for payloads that fail processing, allowing for later inspection and retry. Monitor logs and endpoint health.