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-callbacksdirectory.1.2. Navigate to the Project Directory:
1.3. Configure Environment Variables:
Create a
.envfile 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 32or 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_URLis 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.tsfunction is accessible at/api/sinchCallbacks.3.1. Endpoint Definition:
/api/sinchCallbacks(relative to your application's base URL)POST200 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_reportasper_recipient:3.3. Test the Endpoint Locally:
Test the endpoint using
curlonce deployed or running locally withngrok:You should see logs in your Redwood API server console (
yarn rw dev api) and receive a200 OKresponse.4. Integrate with Sinch (Configuration)
Configure Sinch to send delivery reports to your RedwoodJS endpoint.
4.1. Obtain Your Webhook URL:
ngrokto expose your local development server.yarn rw dev(API runs on port 8911)ngrok http 8911https://forwarding URL (e.g.,https://<random-chars>.ngrok.io). Your callback URL:https://<random-chars>.ngrok.io/api/sinchCallbackshttps://yourapp.yourdomain.com/api/sinchCallbacks)4.2. Configure Callback URL in the Sinch Dashboard:
SINCH_SERVICE_PLAN_IDin.env).envfile (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_reportparameter set to"per_recipient"in your request body.Example: Sending SMS via Sinch REST API (Conceptual
curl)Setting
delivery_reportto"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.envand 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.tsto includetry...catchblocks and detailed logging:Key Improvements:
SinchRecipientDeliveryReportinterfaceloggerwith context (requestId), logging key events and errorstry...catchblocks for signature validation, parsing, and service calls405,401,400,500,200)200 OKonce 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.prismaand define the models:Key Decisions:
MessageModel: Represents messages your application sent, includessinchBatchIdandclientReferenceto link back to Sinch, and tracks latest statusSmsStatusUpdateModel: Represents callbacks from Sinch, linked toMessageviamessageId, stores key fields andrawPayloadsinchBatchId,recipient, andmessageIdonDelete: Cascade: Deletes related status updates when a message is deleted6.2. Apply Database Migrations:
Apply schema changes to your database:
Answer
yif 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.tsand add therecordSmsStatusUpdatefunction:Key Service Logic:
MessageusingsinchBatchIdandrecipient(requires matching formats – E.164 recommended)SmsStatusUpdaterecords if Sinch retriesSmsStatusUpdatelinked to theMessagelatestStatusonMessageif incoming status is newerEnsure
recordSmsStatusUpdateis 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-timestampandx-sinch-signature(case-insensitive)crypto.timingSafeEqualto prevent timing attacks7.2. Integrate Validation into the Handler:
The
validateSinchSignaturefunction 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_SECRETmatches exactly in both.envand 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_recipientandper_recipient_finaldelivery reports?A:
per_recipientsends callbacks for every status change (Dispatched, Delivered, Failed), whileper_recipient_finalonly sends the final status once delivery completes or fails. Useper_recipientfor detailed tracking andper_recipient_finalto 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_mmsand 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
statusCodein 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.