Frequently Asked Questions
Create a RedwoodJS API function as a webhook endpoint, set up database models with Prisma to store message delivery data, and use a service to process incoming Sinch notifications. This allows your application to receive real-time delivery status updates for SMS messages sent via the Sinch API and store them persistently.
Sinch delivery reports provide real-time updates on the status of sent SMS messages, moving beyond "fire and forget" API calls. Knowing whether a message was dispatched, delivered, or failed allows for better handling of communication failures and logging.
HMAC signature validation ensures the authenticity of incoming webhooks, confirming they originate from Sinch and haven't been tampered with. This adds a crucial layer of security to your application by verifying the integrity of the callback data.
Delivery reports must be requested when you send the initial SMS message via the Sinch API. Include the 'delivery_report' parameter set to 'per_recipient' in the API request body to receive updates for each recipient.
The RedwoodJS API endpoint for Sinch callbacks is typically '/api/sinchCallbacks'. This endpoint, created as a RedwoodJS function, receives POST requests from Sinch containing delivery status updates.
Create a '.env' file in your RedwoodJS project root. Add your 'DATABASE_URL', 'SINCH_SERVICE_PLAN_ID', 'SINCH_API_TOKEN', and a randomly generated 'SINCH_WEBHOOK_SECRET', which will also be configured in your Sinch dashboard for security.
The integration uses RedwoodJS for the API and Prisma ORM, the Sinch SMS API for messaging, Prisma for database interactions, and Node.js as the runtime environment. Ngrok is optional for local testing.
Use ngrok to create a public URL for your local server. Then, use curl to send POST requests with sample JSON payloads to your ngrok URL, simulating Sinch callbacks. You should see logs and receive a 200 OK response.
A Sinch SMS callback includes the message type, batch ID, recipient number, status code, delivery status (e.g., 'Dispatched', 'Delivered'), timestamps, and an optional client reference.
Log in to your Sinch Dashboard, navigate to APIs -> SMS APIs, and select your Service Plan ID. Under Callback URLs, add your ngrok URL or production URL. Importantly, add your Webhook Secret to secure the connection.
The Prisma schema includes a 'Message' model to store sent messages and an 'SmsStatusUpdate' model to store the delivery status updates received from Sinch. These models are linked, and indices are included for efficient querying.
The RedwoodJS service handles the logic for processing Sinch callback data, including finding the original message, creating the status update record, and optionally updating the latest status on the message. It also contains error handling and logging.
Implement try-catch blocks for signature validation, parsing, and service calls. Provide detailed logging with request IDs and context for debugging and return appropriate HTTP status codes. Ensure a 200 OK is sent to Sinch even if internal processes fail, to avoid retries.
Integrate Sinch SMS delivery status callbacks into your RedwoodJS application to receive real-time updates on message delivery. Track message success, handle failures, and maintain accurate communication logs – transforming SMS from a "fire and forget" operation into a trackable, auditable communication channel.
Build a RedwoodJS API function that acts as a webhook endpoint to receive notifications from Sinch, then process and store these notifications in your database using Prisma.
What Are SMS Delivery Status Callbacks?
SMS delivery status callbacks (also called delivery receipts or delivery reports) are webhook notifications that SMS providers send to your application when message status changes. Unlike basic SMS sending where you only know the API accepted your request, delivery callbacks provide real-time updates throughout the message lifecycle:
Project Overview and Goals
What You'll Build:
/api/sinchCallbacks
) to receive POST requests from SinchProblem Solved:
Sending an SMS via an API is often a "fire and forget" operation. You know the request was accepted, but you don't know if the message reached the recipient's handset. Delivery status callbacks solve this by pushing status updates (like
Dispatched
,Delivered
,Failed
) back to your application, giving you complete visibility into the message lifecycle.Technologies Used:
ngrok
(optional): For testing webhook callbacks locally during developmentSystem Architecture:
/api/sinchCallbacks
).Prerequisites:
Expected Outcome:
Your RedwoodJS application will:
1. Set Up the RedwoodJS Project
Create a new RedwoodJS project and configure the basic structure and environment.
1.1. Create RedwoodJS Project:
This scaffolds a new RedwoodJS project in the
sinch-redwood-callbacks
directory.1.2. Navigate to the Project Directory:
1.3. Configure Environment Variables:
Create a
.env
file in the project root:Add the following variables:
DATABASE_URL
: Configure this to point to your database. For local development, use SQLite (file:./dev.db
).SINCH_SERVICE_PLAN_ID
/SINCH_API_TOKEN
: Find these in your Sinch Customer Dashboard under APIs > SMS APIs > [Your Service Plan Name].SINCH_WEBHOOK_SECRET
: Generate a strong, unique secret (at least 32 characters). Configure this same secret in the Sinch Dashboard later. Useopenssl rand -hex 32
or any method that produces a cryptographically strong random string.1.4. Initialize the Database:
Apply the initial Prisma schema and create the database file (if using SQLite):
This initializes the database, creates the migration history table, and verifies your
DATABASE_URL
is configured correctly.2. Implement Core Functionality (Callback Handler)
Create an API endpoint (a RedwoodJS function) to receive callbacks from Sinch.
2.1. Generate the API Function:
Generate a new TypeScript function for type safety:
This creates
api/src/functions/sinchCallbacks.ts
.2.2. Basic Handler Structure:
Open
api/src/functions/sinchCallbacks.ts
:This basic structure checks if the request method is POST. You'll add more logic in subsequent steps.
3. Build the API Layer
API functions automatically map to endpoints. The
api/src/functions/sinchCallbacks.ts
function is accessible at/api/sinchCallbacks
.3.1. Endpoint Definition:
/api/sinchCallbacks
(relative to your application's base URL)POST
200 OK
: Callback received and successfully processed (or acknowledged even if internal processing fails, to prevent Sinch retries)400 Bad Request
: Invalid request body (e.g., malformed JSON)401 Unauthorized
: Invalid or missing HMAC signature405 Method Not Allowed
: Request method was not POST500 Internal Server Error
: Unexpected server-side error during processing3.2. Example Sinch Callback Payload:
Sinch sends a POST request with a JSON body like this when you configure
delivery_report
asper_recipient
:3.3. Test the Endpoint Locally:
Test the endpoint using
curl
once deployed or running locally withngrok
:You should see logs in your Redwood API server console (
yarn rw dev api
) and receive a200 OK
response.4. Integrate with Sinch (Configuration)
Configure Sinch to send delivery reports to your RedwoodJS endpoint.
4.1. Obtain Your Webhook URL:
ngrok
to expose your local development server.yarn rw dev
(API runs on port 8911)ngrok http 8911
https://
forwarding URL (e.g.,https://<random-chars>.ngrok.io
). Your callback URL:https://<random-chars>.ngrok.io/api/sinchCallbacks
https://yourapp.yourdomain.com/api/sinchCallbacks
)4.2. Configure Callback URL in the Sinch Dashboard:
SINCH_SERVICE_PLAN_ID
in.env
).env
file (SINCH_WEBHOOK_SECRET
)Note: The exact UI and field names may vary. Refer to the Sinch documentation for current instructions.
4.3. Request Delivery Reports When Sending SMS:
Sinch only sends callbacks if you request them when sending the message. Include the
delivery_report
parameter set to"per_recipient"
in your request body.Example: Sending SMS via Sinch REST API (Conceptual
curl
)Setting
delivery_report
to"per_recipient"
tells Sinch to send a separate callback to your configured URL for status updates concerning each recipient in the batch. You can also use"per_recipient_final"
to only get the final status (e.g.,Delivered
,Failed
)."per_recipient"
provides intermediate statuses too (likeDispatched
).Note: The logic for sending SMS messages like the example above typically resides within a RedwoodJS service function, possibly triggered by a mutation, background job, or another application event.
Environment Variable Summary:
SINCH_SERVICE_PLAN_ID
: Identifies your Sinch service plan (obtained from Sinch Dashboard)SINCH_API_TOKEN
: Authenticates your API requests to Sinch for sending SMS (obtained from Sinch Dashboard)SINCH_WEBHOOK_SECRET
: Authenticates requests from Sinch to your webhook (you create this and configure it in both.env
and Sinch Dashboard)5. Implement Error Handling and Logging
Robust error handling and logging are essential for a reliable webhook.
5.1. Update the Handler Function:
Modify
api/src/functions/sinchCallbacks.ts
to includetry...catch
blocks and detailed logging:Key Improvements:
SinchRecipientDeliveryReport
interfacelogger
with context (requestId
), logging key events and errorstry...catch
blocks for signature validation, parsing, and service calls405
,401
,400
,500
,200
)200 OK
once validated and parsed, even if downstream processing fails (prevents Sinch retries)6. Create the Database Schema and Data Layer
Create database tables to store messages and status updates.
6.1. Define Prisma Schema:
Open
api/db/schema.prisma
and define the models:Key Decisions:
Message
Model: Represents messages your application sent, includessinchBatchId
andclientReference
to link back to Sinch, and tracks latest statusSmsStatusUpdate
Model: Represents callbacks from Sinch, linked toMessage
viamessageId
, stores key fields andrawPayload
sinchBatchId
,recipient
, andmessageId
onDelete: Cascade
: Deletes related status updates when a message is deleted6.2. Apply Database Migrations:
Apply schema changes to your database:
Answer
y
if prompted to confirm.6.3. Generate the Data Layer (Service):
Create a RedwoodJS service:
This creates
api/src/services/smsStatus/smsStatus.ts
.6.4. Implement the Service Logic:
Open
api/src/services/smsStatus/smsStatus.ts
and add therecordSmsStatusUpdate
function:Key Service Logic:
Message
usingsinchBatchId
andrecipient
(requires matching formats – E.164 recommended)SmsStatusUpdate
records if Sinch retriesSmsStatusUpdate
linked to theMessage
latestStatus
onMessage
if incoming status is newerEnsure
recordSmsStatusUpdate
is imported inapi/src/functions/sinchCallbacks.ts
(as shown in Section 5.1).7. Add Security Features (HMAC Validation)
Secure your webhook endpoint to ensure requests genuinely come from Sinch using HMAC-SHA256 signature validation.
7.1. Create a Security Utility:
Create
api/src/lib/sinchSecurity.ts
:Key Security Logic:
x-sinch-timestamp
andx-sinch-signature
(case-insensitive)crypto.timingSafeEqual
to prevent timing attacks7.2. Integrate Validation into the Handler:
The
validateSinchSignature
function is already integrated in the handler code from Section 5.1. Ensure raw body and headers are passed correctly.8. Testing and Debugging Your SMS Webhook
Before deploying to production, thoroughly test your webhook implementation locally and in staging environments.
8.1. Local Testing with ngrok:
delivery_report: "per_recipient"
8.2. Common Debugging Issues:
SINCH_WEBHOOK_SECRET
matches exactly in both.env
and Sinch Dashboard (including Base64 encoding)+15551234567
)delivery_report: "per_recipient"
when sending the SMS8.3. Monitoring Production Webhooks:
Related Resources
Frequently Asked Questions
Q: What's the difference between
per_recipient
andper_recipient_final
delivery reports?A:
per_recipient
sends callbacks for every status change (Dispatched, Delivered, Failed), whileper_recipient_final
only sends the final status once delivery completes or fails. Useper_recipient
for detailed tracking andper_recipient_final
to reduce webhook traffic.Q: How long should I wait before considering a message failed?
A: Sinch typically delivers status updates within minutes, but carrier delays can extend this to hours. Messages that remain in "Dispatched" status for 24-48 hours are likely failed. Sinch will send a final "Expired" status if delivery times out.
Q: Can I use this webhook handler for MMS delivery reports?
A: Yes, with minor modifications. Change the type check to include
recipient_delivery_report_mms
and update your database schema to track MMS-specific fields if needed.Q: Should I retry failed messages automatically?
A: It depends on the failure reason. Check the
statusCode
in the callback – some codes indicate permanent failures (invalid number), while others may be temporary (network issues). Implement retry logic with exponential backoff for temporary failures only.Q: How do I handle webhooks during local development?
A: Use ngrok (as shown in section 4.1) to create a public HTTPS URL that forwards to your local development server. Update the callback URL in Sinch Dashboard to your ngrok URL while developing, then switch to your production URL when deploying.