Frequently Asked Questions
Set up a Fastify server with a POST route '/webhooks/sinch' to handle incoming webhooks from Sinch. Configure your Sinch account to send notifications to this endpoint. The provided code example includes a schema to validate incoming requests and detailed logging for debugging.
The schema defines the expected structure of the JSON payload sent by Sinch, including 'from', 'to', 'body', 'id', and 'type'. This ensures type safety and allows Fastify to validate incoming requests, preventing errors from malformed data. Refer to the article's code example for the detailed schema and always cross-check with official Sinch documentation.
HTTPS encrypts data in transit, protecting sensitive information like message content. When testing locally with ngrok, it provides the necessary HTTPS tunnel. In production, use a reverse proxy or platform with SSL termination.
Use ngrok during local development to expose your server publicly so Sinch can send webhooks to it. For production, deploy your application to a server or platform with a public HTTPS URL and configure that in your Sinch dashboard.
Yes, the provided code includes the 'sendSmsReply' function using axios and the Sinch REST API. You'll need to configure your Sinch Service Plan ID, API Token, and Sinch Number in the '.env' file. Remember to uncomment the relevant line in the webhook handler to enable automatic replies after receiving a message.
Wrap your webhook handler logic in a try...catch block to handle potential errors. Log errors using request.log.error for debugging and inform Sinch there was an issue by sending a 500 Internal Server Error response to prevent retry exhaustion.
Fastify is a high-performance Node.js web framework known for its speed and extensibility. It's an excellent choice for handling Sinch SMS webhooks due to its efficiency in processing requests.
The article provides a comprehensive guide using Node.js, Fastify, and the Sinch SMS API. You'll need to install dependencies, configure environment variables, implement a webhook handler, and set up the callback URL in your Sinch dashboard.
Use ngrok to create a public HTTPS URL for your local server. Configure this URL as the callback in Sinch. Then, you can use curl to simulate Sinch webhook requests and test your application's response.
Storing sensitive information like API keys in environment variables (via .env) prevents them from being exposed in your codebase, improving security and adhering to best practices. Ensure '.env' is in your '.gitignore' file.
The Sinch Service Plan ID is a unique identifier for your Sinch account and SMS service configuration. It's required when making API calls to Sinch, including sending replies and setting up webhooks.
Sinch automatically handles long message concatenation. Your webhook will receive the complete message body, even if it was sent as multiple parts. Similarly, Sinch manages splitting long messages when sending.
Use HTTPS, validate webhook requests with a schema or token, manage credentials securely with environment variables, implement rate limiting, and consider HMAC signature verification if available from Sinch (or use a shared secret as an alternative).
Build Inbound Two-Way SMS with Sinch, Node.js & Fastify
Build a production-ready Node.js application using Fastify to receive and process inbound SMS messages via the Sinch SMS API. This guide covers project setup, webhook handling, sending replies, security considerations, deployment, and troubleshooting.
You'll build a functional Fastify application capable of:
Target Audience: Developers familiar with Node.js and basic web concepts who want to integrate Sinch SMS capabilities into their applications. Familiarity with Fastify helps but isn't required.
Technologies Used:
Prerequisites:
npm install -g ngrok
) or available in your PATHSystem Architecture:
The system follows a simple webhook pattern:
/webhooks/sinch
)1. Setting Up the Project
Initialize your Node.js project and install the necessary dependencies.
1. Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
2. Initialize Node.js Project: Create a
package.json
file to manage dependencies and project metadata. Accept the defaults or fill them in.3. Install Dependencies: Install Fastify for the web server, axios to make API calls to Sinch for replies, and dotenv to manage environment variables.
4. Create Project Structure: Create the basic files and directories.
index.js
: The main entry point for your Fastify application.env
: Stores sensitive credentials and configuration (API keys, phone numbers). Never commit this file to version control..gitignore
: Specifies intentionally untracked files that Git should ignore5. Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.6. Set Up Environment Variables: Open the
.env
file and add the following variables. Replace the placeholder values with your actual Sinch credentials and number.Why this setup? Using environment variables (dotenv) keeps sensitive credentials out of your codebase, adhering to security best practices. The
.gitignore
file ensures these secrets and bulkynode_modules
aren't accidentally committed. Listening on0.0.0.0
makes the server accessible within Docker containers or cloud environments.2. Implementing Core Functionality: The Webhook Handler
Build the Fastify server and the endpoint that receives incoming SMS messages from Sinch.
1. Basic Fastify Server: Open
index.js
and set up a minimal Fastify server.sinchInboundSmsSchema
. If validation fails, Fastify sends a 400 Bad Request response, protecting your handler from invalid data.request.body
for easy debugging during development. Also log key extracted information.from
(sender),to
(your Sinch number), andbody
(the message text).200 OK
response back to Sinch quickly to acknowledge receipt. Failure to respond or sending error codes repeatedly might cause Sinch to disable the webhook.try…catch
block handles unexpected errors during processing, logs them, and sends a generic500 Internal Server Error
response.Endpoint Documentation & Testing:
Endpoint:
POST /webhooks/sinch
Description: Receives inbound SMS notifications from the Sinch platform
Request Body:
application/json
from
(string),to
(string),body
(string, max 2000 chars),id
(string),type
(string, "mo_text"),received_at
(ISO-8601 date-time)sinchInboundSmsSchema
in the code above for the full expected structureResponses:
200 OK
: Successfully received and acknowledged the message. Sinch expects response within the2xx
success range.400 Bad Request
: The incoming request body did not match the expected schema (handled automatically by Fastify). This counts as a permanent failure and will NOT trigger retries.429 Too Many Requests
: The callback will be retried AND the callback throughput will be lowered by Sinch. Source: Sinch Webhooks Documentation (2024)500 Internal Server Error
: An error occurred while processing the message on the server. A5xx
status code will trigger Sinch's retry mechanism.Testing with curl: Simulate a Sinch webhook call using curl once your server is running. Check your running server's console logs to see the output.
3. Integrating with Sinch (Sending Replies)
Implement the function to send replies using the Sinch REST API and axios.
1. Add axios: Already installed in the setup phase.
2. Create the Send Function: Add this function to your
index.js
file, replacing the placeholder comment.SINCH_REGION
environment variable for flexibilityfrom
), the recipient's number (to
– as an array), and the messagebody
Authorization: Bearer YOUR_API_TOKEN
header authenticates with the Sinch REST APIaxios.post
to send the requestcatch
block provides detailed logging based on the type of error received from axios, helpful for troubleshooting API integration issuesrequest.log
instance (or the globalfastify.log
) intosendSmsReply
so logs related to sending a reply are associated with the original incoming request context3. Sinch Dashboard Configuration: Tell Sinch where to send incoming SMS notifications.
https://<your-ngrok-subdomain>.ngrok.io/webhooks/sinch
https://
. Sinch requires secure callbacks.Environment Variables Summary:
SINCH_SERVICE_PLAN_ID
: Your specific service plan identifier from the Sinch dashboard. Used in the API URL for sending.SINCH_API_TOKEN
: Your secret API token from the Sinch dashboard. Used in theAuthorization
header for sending.SINCH_NUMBER
: Your provisioned Sinch virtual phone number in E.164 format (e.g.,+12223334444
). Used as thefrom
number when sending replies.PORT
: The local port your Fastify server listens on (e.g.,3000
).HOST
: The network interface to bind to (0.0.0.0
for broad accessibility,127.0.0.1
for local only).SINCH_REGION
: The geographical region for your Sinch API endpoint (e.g.,us
,eu
). Affects the base URL for sending SMS. Check Sinch documentation for your correct region if notus
.4. Error Handling, Logging, and Retries
Error Handling:
/webhooks/sinch
route. Invalid requests get a 400 response.try…catch
block in the webhook handler catches errors during message processing (e.g., database errors, errors callingsendSmsReply
). It logs the error and returns a 500 status to Sinch.sendSmsReply
): Thecatch
block insendSmsReply
handles network errors, authentication failures (401), authorization issues (403), bad requests (400), or server errors (5xx) from the Sinch API. It logs detailed information.Logging:
Fastify Default Logger (Pino): Enabled via
logger: true
during Fastify initialization. Provides structured JSON logging by default, excellent for production.Key Events Logged:
fastify.log.info
)request.log.info
)request.log.info
)request.log.info
)request.log.error
)log.info
insendSmsReply
)log.info
/log.error
insendSmsReply
)Log Analysis: In production, forward these structured logs to a log management service (e.g., Datadog, Logz.io, ELK stack) for searching, filtering, and alerting based on error messages or specific log properties.
Retry Mechanisms:
2xx
status code, Sinch retries the callback using exponential backoff. Official retry schedule: The first retry occurs 5 seconds after the initial attempt, then 10 seconds, 20 seconds, 40 seconds, 80 seconds, doubling on every attempt. The final retry occurs at 81,920 seconds (22 hours 45 minutes) after the initial failed attempt. For429
responses, the callback will be retried with lowered throughput. Ensure your handler responds quickly (ideally under 2–3 seconds) to avoid triggering retries. Source: Sinch Webhooks Documentation (2024)sendSmsReply
): Retries are not implemented in the providedsendSmsReply
function. For production, consider adding retries with exponential backoff for transient network errors or temporary Sinch API issues (e.g., 503 Service Unavailable). Libraries likeasync-retry
can simplify this. Be cautious retrying 4xx errors (like 400 Bad Request or 401 Unauthorized) as they usually indicate a persistent problem.5. Database Schema and Data Layer (Conceptual)
Storing message history is often required. Here's a conceptual approach:
Entity Relationship Diagram (ERD) – Simple:
Implementation Steps (using an ORM like Prisma or Sequelize):
npm install @prisma/client pg
(for Prisma + PostgreSQL)npx prisma init
prisma/schema.prisma
with models based on the ERDnpx prisma migrate dev --name initial_setup
npx prisma generate
try
block:Contact
based onsenderNumber
Message
record withdirection: 'inbound'
, linking it to the contact, storing themessageContent
,messageId
, etc.sendSmsReply
:Message
record withdirection: 'outbound'
,status: 'sent'
, linking to the contact and storing thebatch_id
returned by Sinchstatus
todelivered
orfailed
Performance: Index
phone_number
onContact
andsinch_msg_id
,contact_id
onMessage
. Use database connection pooling (handled by most ORMs).6. Security Features
/webhooks/sinch
route. Protects against malformed payloads..env
and ensuring it's in.gitignore
.@fastify/rate-limit
:Shared Secret Token (Simpler): Include a hard-to-guess secret token as a query parameter in the Callback URL configured in Sinch (e.g.,
https://…/webhooks/sinch?token=YOUR_SECRET_TOKEN
). Verify this token in your handler. This adds protection but is less robust than HMAC.HMAC-SHA256 (Most Secure, Recommended for Production): Once configured by your account manager, Sinch includes signature headers in webhook requests. Your application calculates the signature using the webhook secret and compares it to Sinch's provided signature. Typical headers include:
x-sinch-webhook-signature-nonce
: Unique nonce valuex-sinch-webhook-signature-timestamp
: Request timestampx-sinch-webhook-signature
: HMAC-SHA256 signature (base64-encoded)HMAC Verification Example:
Note: Contact your Sinch account manager to enable and configure HMAC authentication for your webhooks.
7. Handling Special Cases
body
received by your webhook contains the fully reassembled message. When sending, Sinch also handles splitting if thebody
exceeds standard SMS limits.mo_text
). Handling MMS (images, etc.) or binary SMS requires different webhook types (mo_binary
) and payload structures, often involving links to media content. This requires extending the schema and handler logic.sendSmsReply
function might fail if thesenderNumber
is invalid or cannot receive SMS. The error handling block catches this (often as a 4xx error from Sinch).request.body.id
) to deduplicate messages if necessary (e.g., by checking if a message with that ID already exists in your database before processing). Inbound messages are stored in Sinch's system for 14 days and can be retrieved via the Inbounds API if needed. Source: Sinch Inbounds API (2024)8. Performance Optimizations
async/await
, preventing the Node.js event loop from being blockedinfo
orwarn
instead ofdebug
to reduce logging overhead (fastify({ logger: { level: 'info' } })
)k6
,autocannon
, orwrk
to simulate high volumes of webhook traffic and identify bottlenecks in your processing logic or downstream dependencies9. Monitoring, Observability, and Analytics
GET /
route serves as a basic health check endpoint for load balancers or monitoring systems10. Deployment Considerations
.dockerignore
: Similar to.gitignore
, includenode_modules
,.env
,Dockerfile
,.dockerignore
,*.log
SINCH_API_TOKEN
, etc.) securely using platform secrets management, not by hardcoding them in the Docker image or codenpm install pm2 -g
) to keep the Node.js application running, manage logs, and handle restarts (pm2 start index.js
)localhost:3000
11. Troubleshooting
http://localhost:4040
by default) to see incoming requests/webhooks/sinch
path.env
variables are set correctly and accessible by the application (especially in deployment environments). Log them on startup (excluding secrets) for verification if neededsendSmsReply
function'scatch
block (Status, Headers, Data) to understand failures when sending replies. Consult the Sinch API documentation for specific error codesThis guide provides a solid foundation for receiving Sinch SMS messages with Fastify. Adapt the business logic, error handling, security measures, and deployment strategy to your specific production requirements.