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.
This guide provides a step-by-step walkthrough for integrating Sinch SMS delivery status callbacks into your RedwoodJS application. By implementing this, your application can programmatically receive real-time updates on the delivery status of SMS messages sent via the Sinch API, enabling you to track message success, handle failures, and maintain accurate communication logs.
We will build a RedwoodJS API function that acts as a webhook endpoint to receive notifications from Sinch. These notifications will then be processed and stored in a database using Prisma, providing a persistent record of message delivery events.
Project Overview and Goals
What We're Building:
/api/sinchCallbacks
) to receive POST requests from Sinch containing SMS delivery status updates.Problem 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 actually reached the recipient's handset. Sinch delivery status callbacks solve this by pushing status updates (like
Dispatched
,Delivered
,Failed
) back to your application, providing crucial visibility into the message lifecycle.Technologies Used:
ngrok
: For testing webhook callbacks locally during development.System Architecture:
The basic flow is as follows:
/api/sinchCallbacks
).Prerequisites:
yarn global add redwoodjs-cli
ornpm install -g redwoodjs-cli
)Expected Outcome:
Upon completion, your RedwoodJS application will have a robust mechanism to:
1. Setting up the RedwoodJS Project
Let's start by creating a new RedwoodJS project and setting up the basic structure and environment.
1.1. Create RedwoodJS Project:
Open your terminal and run:
This command scaffolds a new RedwoodJS project in the
sinch-redwood-callbacks
directory.1.2. Navigate into Project Directory:
1.3. Configure Environment Variables:
RedwoodJS uses a
.env
file for environment variables. Create this file in the project root:Add the following variables. We'll explain how to get the Sinch values shortly.
DATABASE_URL
: Configure this to point to your chosen database. For local development, SQLite (file:./dev.db
) is often sufficient.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). You will configure this same secret in the Sinch Dashboard later. This is crucial for validating incoming webhooks. Using tools likeopenssl rand -hex 32
is common, but any method that produces a cryptographically strong random string is acceptable.1.4. Initialize Database:
RedwoodJS uses Prisma for database management. Let's apply the initial schema (which is mostly empty at this point) and create the database file (if using SQLite).
This command initializes the database, creates the migration history table, and ensures your
DATABASE_URL
is working.2. Implementing Core Functionality (Callback Handler)
We need an API endpoint (a RedwoodJS Function) to receive the callbacks from Sinch.
2.1. Generate the API Function:
Use the RedwoodJS CLI to generate a new function. We'll use TypeScript for better type safety.
This creates
api/src/functions/sinchCallbacks.ts
and related files.2.2. Basic Handler Structure:
Open
api/src/functions/sinchCallbacks.ts
. The initial structure will look something like this:This basic structure checks if the request method is POST. We will add more logic in subsequent steps.
3. Building the API Layer
In RedwoodJS, API functions automatically map to endpoints. The function
api/src/functions/sinchCallbacks.ts
will be accessible at the URL path/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 signature.405 Method Not Allowed
: Request method was not POST.500 Internal Server Error
: Unexpected server-side error during processing.3.2. Example Sinch Callback Payload (
recipient_delivery_report_sms
):Sinch will send a POST request with a JSON body similar to this when you configure
delivery_report
asper_recipient
:3.3. Testing the Endpoint Locally (Simulating Sinch):
Once deployed or running locally with
ngrok
, you can test the endpoint usingcurl
:You should see logs in your Redwood API server console (
yarn rw dev api
) and receive a200 OK
response (based on the placeholder code).4. Integrating with Sinch (Configuration)
Now, configure Sinch to send delivery reports to your RedwoodJS endpoint.
4.1. Obtain Your Webhook URL:
ngrok
to expose your local development server to the internet.yarn rw dev
(Note the API port, usually 8911)ngrok http 8911
https://
forwarding URL provided by ngrok (e.g.,https://<random-chars>.ngrok.io
). Your full callback URL will behttps://<random-chars>.ngrok.io/api/sinchCallbacks
.https://yourapp.yourdomain.com/api/sinchCallbacks
).4.2. Configure Callback URL in Sinch Dashboard:
SINCH_SERVICE_PLAN_ID
in your.env
)..env
file forSINCH_WEBHOOK_SECRET
.(Note: The exact UI and field names in the Sinch dashboard may vary. Refer to the Sinch documentation for the most up-to-date instructions on configuring callback URLs and secrets for your specific service plan.)
4.3. Request Delivery Reports When Sending SMS:
Crucially, Sinch only sends callbacks if you request them when sending the message. When using the Sinch SMS API (e.g., the REST API) to send a 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 would typically reside 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 (e.g., for sending SMS). Obtained from Sinch Dashboard.SINCH_WEBHOOK_SECRET
: Authenticates requests from Sinch to your webhook. You create this secret and configure it in both your.env
and the Sinch Dashboard.5. Implementing 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
.logger
with context (requestId
). Logging key events and errors. Added check to avoid logging full potentially sensitive body on parse failure.try...catch
blocks for signature validation, parsing, and service calls. Added checks forevent.body
existence andSINCH_WEBHOOK_SECRET
configuration before validation.405
,401
,400
,500
,200
).200 OK
once the request is validated and parsed, even if downstream processing fails. This is crucial to prevent Sinch from resending the same callback. Log internal errors aggressively for debugging.6. Creating the Database Schema and Data Layer
We need database tables to store information about messages sent and the status updates received.
6.1. Define Prisma Schema:
Open
api/db/schema.prisma
and define the models. We'll add aMessage
table (assuming your app tracks sent messages) and anSmsStatusUpdate
table.Key Decisions:
Message
Model: Represents the message your application sent. IncludessinchBatchId
and optionallyclientReference
to link back to Sinch. Added fields (latestStatus
, etc.) for quick access to the most recent status. Added comment emphasizing E.164 format forto
field.SmsStatusUpdate
Model: Represents a single callback received from Sinch. Linked to theMessage
viamessageId
. Stores key fields from the callback and therawPayload
.sinchBatchId
,recipient
, andmessageId
.onDelete: Cascade
: Added a comment explaining the implication and suggesting consideration of alternatives based on application requirements.6.2. Apply Database Migrations:
Run the migration command to apply these schema changes to your database:
Answer
y
(yes) if prompted to confirm the migration.6.3. Generate the Data Layer (Service):
Create a RedwoodJS service to encapsulate the logic for handling the status updates and interacting with the database.
This creates
api/src/services/smsStatus/smsStatus.ts
and related test files.6.4. Implement the Service Logic:
Open
api/src/services/smsStatus/smsStatus.ts
and add therecordSmsStatusUpdate
function.Key Service Logic:
Message
record usingsinchBatchId
andrecipient
. Explicitly states the requirement for matching recipient formats (E.164 recommended) and handles the case where the message isn't found with clearer logging.SmsStatusUpdate
records if Sinch sends the same callback multiple times (due to retries). Refined the check to focus on core status details rather than exact timestamp.SmsStatusUpdate
linked to the foundMessage
.latestStatus
fields on theMessage
record if the incoming status is newer than the currently stored latest status. Handles same-timestamp case.Now, ensure the
recordSmsStatusUpdate
function is correctly imported and called within theapi/src/functions/sinchCallbacks.ts
handler (as shown in the updated handler code in Section 5.1).7. Adding Security Features (HMAC Validation)
Securing your webhook endpoint is critical to ensure that requests genuinely come from Sinch and haven't been tampered with. We'll use HMAC-SHA256 signature validation.
7.1. Create a Security Utility:
It's good practice to place security-related functions in the
api/src/lib
directory.Create a new file:
api/src/lib/sinchSecurity.ts
Key Security Logic:
x-sinch-timestamp
andx-sinch-signature
headers (case-insensitive).crypto.timingSafeEqual
to compare the provided signature with the expected signature, preventing timing attacks. Handles potential buffer length mismatches and Base64 decoding errors.7.2. Integrate Validation into the Handler:
Ensure the
validateSinchSignature
function is called within theapi/src/functions/sinchCallbacks.ts
handler before parsing the body or processing the data. This was already included in the updated handler code in Section 5.1. Make sure the raw body and headers are passed correctly.(End of provided text)