Connect FlutterFlow to Sinch by routing messages through a Firebase Cloud Function that holds your Sinch API credentials and calls the Sinch Conversation API. Because Sinch's OAuth Access Tokens expire roughly every hour, refreshing them client-side is impractical — the Cloud Function handles token management. Your FlutterFlow app calls the function with the recipient, message text, and an optional channel priority order (e.g., RCS then SMS fallback), consuming Sinch messaging credits per send.
| Fact | Value |
|---|---|
| Tool | Sinch |
| Category | Communication |
| Method | FlutterFlow API Call |
| Difficulty | Advanced |
| Time required | 60 minutes |
| Last updated | July 2026 |
Sinch Conversation API and the Cloud Function Requirement
Sinch's Conversation API provides a unified endpoint to reach users across SMS, RCS, WhatsApp, MMS, and more — all from a single POST /messages:send call. The key differentiator is channel_priority_order: you specify a list like ["RCS","SMS"] and Sinch automatically tries RCS first, falling back to SMS if the recipient's device does not support it. This removes the need to detect device capabilities in your app.
Authentication uses either a static API Token (Service Plan) or OAuth 2.0 (Key ID and Key Secret). With OAuth, access tokens expire approximately every hour — requesting a new token requires your Key Secret. Placing the Key Secret inside a FlutterFlow API Call header would compile it into the app binary, where it can be extracted with widely available tools. A Firebase Cloud Function solves both problems: it holds the Key Secret securely in environment config and fetches a fresh OAuth token automatically before each send.
Sinch offers a free trial with a limited credit balance to test messaging across channels. After the trial, billing is per-message, per-channel, and per-region — check current rates on the Sinch pricing page before planning bulk campaigns. Each channel (SMS, RCS, WhatsApp) also has its own onboarding: for SMS you need a verified sender ID or phone number; for WhatsApp you need approved message templates; for RCS you need carrier registration in your target markets.
Integration method
FlutterFlow cannot safely hold Sinch API credentials — any key placed in an API Call header is bundled inside the compiled Flutter app and can be extracted. Additionally, Sinch's OAuth Access Tokens expire approximately every hour, making client-side token management unreliable. A Firebase Cloud Function holds your Sinch Key ID, Key Secret, and Project ID, fetches a fresh OAuth token on each invocation, and forwards the send request to Sinch's Conversation API. FlutterFlow's API Call hits your function, not Sinch directly.
Prerequisites
- A Sinch account at sinch.com with a Project ID, Access Key ID, and Key Secret from the Customer Dashboard
- A verified sender ID or phone number for SMS, and completed channel onboarding for any other channels (RCS, WhatsApp) you want to use
- Firebase project on the Blaze (pay-as-you-go) plan with Cloud Functions enabled — required for outbound HTTP calls to Sinch
- A FlutterFlow project with at least one screen and a Firestore database set up for logging sent messages
- Basic Node.js familiarity to edit and deploy the Cloud Function
Step-by-step guide
Get your Sinch credentials from the Customer Dashboard
Log in to your Sinch account at dashboard.sinch.com. In the left sidebar, navigate to **Settings** → **Access Keys**. Click **New Key** to create an access key pair — you'll receive a **Key ID** (which you can view later) and a **Key Secret** (shown only once — copy it immediately into a secure location like a password manager). Next, note your **Project ID** — it's visible at the top of the Customer Dashboard under your account name. You'll need this for all Conversation API calls. Navigate to **Conversation API** → **Apps** in the left sidebar and create a Conversation API app. Give it a name and enable the channels you plan to use (SMS, RCS, WhatsApp). For SMS, go to **Numbers** → **Get a Number** to provision a sender number. For WhatsApp, you'll need to go through Meta's Business Manager integration. For RCS, carrier registration is required per target market — Sinch's team can assist. Keep the Key ID, Key Secret, Project ID, and your Sinch App ID handy — all four will be used in the Cloud Function environment configuration. Never paste these into FlutterFlow.
Pro tip: The Key Secret is displayed only once. If you miss it, you'll need to create a new access key. Store it in a password manager immediately after copying.
Expected result: You have your Sinch Project ID, Key ID, Key Secret, and Conversation App ID saved securely and your chosen channels are provisioned.
Deploy a Firebase Cloud Function that proxies sends and manages OAuth tokens
Create a Firebase Cloud Function that handles two responsibilities: acquiring a fresh Sinch OAuth access token and forwarding your FlutterFlow app's send requests to the Sinch Conversation API. Open the Firebase Console for your project, confirm you're on the Blaze plan (free Spark blocks outbound HTTP), and navigate to Functions. In your `functions/index.js`, configure the four Sinch credentials as Firebase environment variables using the Firebase CLI: ``` firebase functions:config:set sinch.key_id="YOUR_KEY_ID" sinch.key_secret="YOUR_KEY_SECRET" sinch.project_id="YOUR_PROJECT_ID" sinch.app_id="YOUR_APP_ID" ``` Then deploy the function shown below. The function first fetches a short-lived OAuth token from Sinch's auth endpoint, then posts to the Conversation API's send endpoint using that token. Token refresh is fully automatic — no client-side timer needed. The `channel_priority_order` array in the request body is the Sinch Conversation API's key differentiator: list channels in priority order and Sinch will try each in sequence until one succeeds. An order of `["RCS","SMS"]` means every capable Android gets an RCS message; everyone else gets SMS — zero additional code required on your side. After deploying, copy the HTTPS trigger URL from the Firebase Console (Functions dashboard → your function name → Trigger URL). This is what FlutterFlow will call.
1// functions/index.js2const functions = require('firebase-functions');3const admin = require('firebase-admin');4const axios = require('axios');56admin.initializeApp();7const db = admin.firestore();89const KEY_ID = functions.config().sinch.key_id;10const KEY_SECRET = functions.config().sinch.key_secret;11const PROJECT_ID = functions.config().sinch.project_id;12const APP_ID = functions.config().sinch.app_id;13const SINCH_AUTH_URL = 'https://auth.sinch.com/oauth2/token';14const SINCH_SEND_URL = `https://us.conversation.api.sinch.com/v1/projects/${PROJECT_ID}/messages:send`;1516async function getSinchToken() {17 const credentials = Buffer.from(`${KEY_ID}:${KEY_SECRET}`).toString('base64');18 const response = await axios.post(SINCH_AUTH_URL,19 'grant_type=client_credentials',20 {21 headers: {22 Authorization: `Basic ${credentials}`,23 'Content-Type': 'application/x-www-form-urlencoded'24 }25 }26 );27 return response.data.access_token;28}2930exports.sinchSend = functions.https.onRequest(async (req, res) => {31 if (req.method !== 'POST') return res.status(405).send('Method Not Allowed');3233 const { to, text, channelPriority } = req.body;34 if (!to || !text) {35 return res.status(400).json({ error: 'to and text are required' });36 }3738 try {39 const token = await getSinchToken();40 const payload = {41 app_id: APP_ID,42 recipient: { identified_by: { channel_identities: [{ channel: 'SMS', identity: to }] } },43 message: { text_message: { text } },44 channel_priority_order: channelPriority || ['RCS', 'SMS']45 };46 const response = await axios.post(SINCH_SEND_URL, payload, {47 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }48 });49 // Log message_id to Firestore50 await db.collection('sinch_messages').add({51 to,52 text,53 messageId: response.data.message_id,54 sentAt: admin.firestore.FieldValue.serverTimestamp()55 });56 return res.status(200).json({ message_id: response.data.message_id });57 } catch (err) {58 const status = err.response ? err.response.status : 500;59 return res.status(status).json({ error: err.message });60 }61});Pro tip: Use `us.conversation.api.sinch.com` for US-region projects or `eu.conversation.api.sinch.com` for EU — using the wrong region returns a 404. Check your project's region in the Sinch Customer Dashboard under Settings.
Expected result: Firebase Console shows the `sinchSend` function deployed with a green checkmark and a working HTTPS trigger URL.
Create the API Call in FlutterFlow targeting your Cloud Function
In the FlutterFlow editor, click **API Calls** in the left navigation panel. Click **+ Add** → **Create API Group**. Name it `SinchProxy` and set the Base URL to your Firebase Cloud Function URL (the base, e.g. `https://us-central1-yourproject.cloudfunctions.net`). Inside the group, click **+ Add API Call** and name it `SendMessage`. Set the method to `POST` and the endpoint to `/sinchSend`. In the **Variables** tab, add three variables: - `to` (String) — the recipient's phone number in E.164 format (e.g., `+14155551234`) - `text` (String) — the message body - `channelPriority` (String, optional) — comma-separated channels in priority order In the **Body** section, choose **JSON** and enter: ```json {"to":"{{to}}","text":"{{text}}","channelPriority":["RCS","SMS"]} ``` Switch to **Response & Test** and paste a sample response: `{"message_id":"01ABC123"}`. Click **Generate JSON Paths** — FlutterFlow will create a path `$.message_id` that you can use in action chains to confirm and log the send. Click **Test API Call**, fill in a real phone number for `to` (in E.164 format) and a test message for `text`, then click **Test**. If your Cloud Function is deployed correctly and Sinch credentials are valid, you'll see `{"message_id":"..."}` in the response — and receive the SMS on your phone within seconds. If you see a 401, the token fetch failed (check Key ID/Secret). If you see a 400, check the phone number format. Do not add any `Authorization` header in FlutterFlow's API Call — authentication is entirely handled inside the Cloud Function.
1// FlutterFlow API Call configuration2{3 "group_name": "SinchProxy",4 "base_url": "https://us-central1-yourproject.cloudfunctions.net",5 "calls": [6 {7 "name": "SendMessage",8 "method": "POST",9 "endpoint": "/sinchSend",10 "headers": {},11 "body_type": "JSON",12 "body": {13 "to": "{{to}}",14 "text": "{{text}}",15 "channelPriority": ["RCS", "SMS"]16 },17 "variables": [18 { "name": "to", "type": "String" },19 { "name": "text", "type": "String" }20 ]21 }22 ]23}Pro tip: Phone numbers must be in E.164 format: `+` followed by country code and number, no spaces or dashes (e.g., `+14155551234`). If your app collects phone numbers without the country code, add a country-code selector in your FlutterFlow form and concatenate before passing to the API Call.
Expected result: The API Call test returns `{"message_id":"..."}` and an SMS (or RCS message) arrives on the test phone number within a few seconds.
Add a confirmation dialog and wire the send action to a button
Sinch messaging credits are consumed on send and cannot be recovered — a misfire (typo in the number, test spam, or accidental double-tap) costs real money. Before wiring the send directly to a button, add a confirmation step. In your FlutterFlow screen, add a `TextField` for the recipient's phone number, a `TextField` for the message body, and a `Button` labeled 'Send Message'. Select the Button, open the **Actions** panel, and click **+ Add Action**. Choose **Alert Dialog** → **Confirm Dialog** with the message 'Are you sure you want to send this message? Messaging credits will be consumed.' Set the confirm label to 'Send' and the cancel label to 'Cancel'. As the 'On Confirm' chained action, add **Backend/API Call** → **SinchProxy → SendMessage**. Bind: - `to`: the phone number `TextField`'s current value (or a Firestore field for the selected recipient) - `text`: the message `TextField`'s current value As a second chained action after the API call, add **Show Snack Bar**: if `$.message_id` is non-empty, show 'Message sent!' — otherwise show 'Send failed. Check the phone number and try again.' For apps that also need to display sent message history, add a **Firestore Query** on the `sinch_messages` collection (ordered by `sentAt` descending) to a `ListView` below the form, so users can see their recent sends without leaving the app.
Pro tip: If your app allows users to enter recipient numbers directly, add client-side validation in FlutterFlow using a Conditional Action: check that the text starts with '+' and has at least 10 digits before calling the API. This prevents obvious input errors before any credits are spent.
Expected result: Tapping the button shows a confirmation dialog; confirming sends the message and shows 'Message sent!' — the `sinch_messages` Firestore collection gains a new document with the message_id.
Handle inbound delivery receipts and configure Sinch webhooks
Sinch can push delivery receipts (delivered, failed, queued) and inbound replies back to a webhook — useful for showing delivery status in your app or handling two-way conversations. Since your FlutterFlow app can't receive HTTP requests, this webhook must be your Firebase Cloud Function. In the Sinch Customer Dashboard, navigate to **Conversation API** → **Apps** → your app → **Webhooks**. Click **Add Webhook** and enter the URL of your Cloud Function's webhook handler (you'll add a second export to `index.js` for this). Select the event triggers: `MESSAGE_DELIVERY` for delivery receipts and `INBOUND_MESSAGE` for replies. Update your Cloud Function to handle these events: on `MESSAGE_DELIVERY`, update the corresponding Firestore document's status field; on `INBOUND_MESSAGE`, write a new document to a `sinch_inbound` collection. Your FlutterFlow app can then subscribe to `sinch_inbound` with a real-time Firestore query to display replies in an inbox screen. If you'd rather skip the Cloud Function and webhook setup entirely, RapidDev's team handles FlutterFlow integrations like this every week — book a free scoping call at rapidevelopers.com/contact.
1// Add to functions/index.js — webhook handler for delivery receipts and inbound messages2exports.sinchWebhook = functions.https.onRequest(async (req, res) => {3 const event = req.body;4 const type = event.type;56 if (type === 'MESSAGE_DELIVERY') {7 const { message_id, status } = event.message_delivery_report || {};8 if (message_id) {9 const snap = await db.collection('sinch_messages')10 .where('messageId', '==', message_id).limit(1).get();11 if (!snap.empty) {12 await snap.docs[0].ref.update({ deliveryStatus: status });13 }14 }15 } else if (type === 'INBOUND_MESSAGE') {16 const msg = event.message;17 await db.collection('sinch_inbound').add({18 from: event.contact_id,19 text: msg && msg.text_message ? msg.text_message.text : '',20 receivedAt: admin.firestore.FieldValue.serverTimestamp()21 });22 }2324 res.status(200).send('ok');25});Pro tip: Register the `sinchWebhook` URL in the Sinch Dashboard only after deploying this updated function. Sinch may send a test ping to the URL on registration — return HTTP 200 to confirm reachability.
Expected result: Delivery status updates appear in the `sinch_messages` Firestore documents and inbound replies appear in the `sinch_inbound` collection, which your FlutterFlow app can display in real time.
Common use cases
Two-factor authentication via SMS with RCS upgrade
A fintech or marketplace app needs to verify users' phone numbers during sign-up. The app calls a Cloud Function that sends a one-time code via RCS (rich, branded message) on Android and falls back to SMS for all other devices — all with a single `channel_priority_order: ["RCS", "SMS"]` configuration.
Build a phone verification screen that sends a 6-digit OTP to the user's phone number using Sinch, showing a branded RCS message on Android and a plain SMS on other devices.
Copy this prompt to try it in FlutterFlow
Order dispatch notifications
A logistics or food delivery app triggers a Sinch send when an order's status changes to 'Out for Delivery'. The message includes the estimated arrival time and a tracking link, delivered to the customer's phone number via the highest available channel — reducing missed deliveries without requiring the customer to have the app open.
When an order status updates to 'dispatched', automatically send an SMS/RCS notification to the customer's phone number with the order number, ETA, and a tracking link.
Copy this prompt to try it in FlutterFlow
Appointment reminders for a booking platform
A healthcare or services booking app stores appointments in Firestore. A scheduled Cloud Function runs daily, finds appointments in the next 24 hours, and sends Sinch messages to each patient — reducing no-shows. The `channel_priority_order` ensures delivery even if the patient's phone doesn't support RCS.
Create a reminder system that sends a Sinch message to patients 24 hours before their appointment, including the appointment time, provider name, and a link to reschedule.
Copy this prompt to try it in FlutterFlow
Troubleshooting
Cloud Function returns 401 Unauthorized on every send
Cause: The Sinch OAuth token fetch is failing — most likely because the Key ID or Key Secret in Firebase environment config is incorrect, or the access key has been disabled in the Sinch Dashboard.
Solution: In the Firebase Console, go to Functions → Configuration and verify the `sinch.key_id` and `sinch.key_secret` values match exactly what's shown in the Sinch Customer Dashboard under Settings → Access Keys. If you suspect the secret was lost, create a new access key (the old one cannot recover its secret), update the Firebase config with `firebase functions:config:set sinch.key_secret="NEW_SECRET"`, and redeploy.
API returns 404 Not Found when calling the Conversation API send endpoint
Cause: The Project ID or App ID is incorrect, or you're using the wrong region host. Sinch has separate US and EU API hosts — calling `us.conversation.api.sinch.com` for an EU-region project returns 404.
Solution: Check your project's region in the Sinch Customer Dashboard (Settings → API). If your project is EU-based, update the `SINCH_SEND_URL` in your Cloud Function to `https://eu.conversation.api.sinch.com/v1/projects/${PROJECT_ID}/messages:send` and redeploy. Also confirm the Project ID and App ID in your function config match the dashboard values exactly.
1// Change the region host in your Cloud Function2const SINCH_SEND_URL = `https://eu.conversation.api.sinch.com/v1/projects/${PROJECT_ID}/messages:send`;Message sends but is never delivered — status stays QUEUED
Cause: The channel (e.g. SMS) is not fully onboarded for the target country. For SMS, you need a verified sender ID or provisioned phone number. For WhatsApp, you need pre-approved message templates. For RCS, you need carrier registration.
Solution: In the Sinch Dashboard, navigate to **Conversation API** → **Apps** → your app and check the channel health indicators. For SMS, verify you have a provisioned number under **Numbers** for the recipient's country. If you see 'sender not approved' errors in Firestore or function logs, contact Sinch support to complete channel onboarding for your target markets.
FlutterFlow API Call test shows XMLHttpRequest error or network error in web preview
Cause: CORS (Cross-Origin Resource Sharing) headers are missing from the Firebase Cloud Function response. Browser-based requests (including FlutterFlow's web preview and Run mode) require CORS headers; native iOS/Android builds do not.
Solution: Install the `cors` npm package in your functions directory and wrap the function handler. Redeploy after adding the CORS middleware.
1const cors = require('cors')({ origin: true });2exports.sinchSend = functions.https.onRequest((req, res) => {3 cors(req, res, async () => {4 // ... existing handler code ...5 });6});Best practices
- Never place Sinch Key ID, Key Secret, or Project ID in a FlutterFlow API Call — these credentials compile into the app bundle. Always keep them in Firebase Cloud Function environment config.
- Refresh the OAuth Access Token inside the Cloud Function on every request (or cache it with a 50-minute TTL). OAuth tokens expire in approximately one hour — a stale token causes 401 errors that are hard to debug from the app side.
- Always add a confirmation dialog in FlutterFlow before triggering a Sinch send — credits are consumed on send and cannot be refunded. Even a typo in a phone number costs money.
- Log the `message_id` returned by Sinch to Firestore immediately after a successful send. This allows you to correlate delivery receipts from the webhook with outbound messages.
- Use `channel_priority_order` to define fallback logic (e.g., `["RCS","SMS"]`) rather than hard-coding a single channel. This ensures maximum deliverability without extra code.
- Complete channel onboarding before testing end-to-end. Each channel (SMS, RCS, WhatsApp) requires separate setup in the Sinch Dashboard — sending on an un-onboarded channel results in QUEUED messages that never deliver.
- Store phone numbers in E.164 format (`+[country code][number]`) in your Firestore database. Validate the format before calling the API to prevent wasting credits on malformed numbers.
- For high-volume sends (hundreds of messages), fan out from the Cloud Function itself using batch logic rather than triggering one FlutterFlow API Call per recipient — this avoids UI timeouts and reduces Firestore write overhead.
Alternatives
Twilio is the most widely documented CPaaS with a larger developer community and more detailed tutorials — a better choice if your team has limited API experience or needs extensive support resources.
Vonage (Nexmo) offers a similar omnichannel API with per-API key/JWT authentication instead of OAuth tokens — slightly simpler credential management if hourly token refresh is a concern.
Plivo provides competitive SMS/voice pricing with a simpler authentication model (fixed auth_id and auth_token) — a good alternative if Sinch's OAuth flow adds too much backend complexity.
Frequently asked questions
Why does the Sinch OAuth token expire every hour and why can't I handle that in FlutterFlow?
Sinch's OAuth access tokens have a short expiry window (approximately one hour) as a security measure — a leaked token has limited blast radius. Refreshing requires your Key Secret, which must never leave a secure server environment. Because FlutterFlow compiles to a client app, storing the Key Secret there would expose it in the app binary. The Firebase Cloud Function refreshes the token automatically on every send, so your app never has to manage token lifecycle.
Can I use Sinch to send WhatsApp messages from FlutterFlow?
Yes, with additional setup. WhatsApp through Sinch requires you to connect your WhatsApp Business Account in the Sinch Dashboard and create pre-approved message templates in Meta's Business Manager. Once onboarded, you add `"WHATSAPP"` to your `channel_priority_order` array and Sinch handles routing. Note that WhatsApp only allows template messages for outbound sends outside of a 24-hour reply window — free-form text only works within 24 hours of a user's last message.
Do I need a separate phone number for each country I want to send SMS to?
Not necessarily. Sinch can provision shared or long-code numbers that work internationally, but deliverability varies by destination country — some countries require local sender IDs. For high-volume sending to specific countries, provisioning a local virtual number or applying for an approved Sender ID improves deliverability. Check the Sinch Dashboard under Numbers → Search for availability in your target markets.
Can my FlutterFlow app receive inbound SMS replies from users?
Not directly — FlutterFlow apps have no server address to receive HTTP webhooks. The pattern is to register your Firebase Cloud Function URL as a Sinch webhook for `INBOUND_MESSAGE` events. When a user replies, Sinch calls your function, which writes the message to a Firestore collection. Your FlutterFlow app then displays the conversation using a real-time Firestore query, updating instantly as new messages arrive.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation