This page walks through the moving parts so you have a mental model of where your code sits and what runs where.
The CLI uploads your source and configuration to the Sinch Functions Platform. From there, the platform builds your function, runs it, and updates any callback URLs on your Voice or Conversation apps so inbound traffic reaches the new version.
- You run
sinch functions deployfrom your project directory. - The CLI validates your project:
- Node: typecheck, bundle with
tsup, smoke-test. - C#:
dotnet build, health-check locally.
- Node: typecheck, bundle with
- The CLI packages the project into a zip and uploads it to the Sinch Functions Platform.
- The platform builds and rolls out your function. If an identical build already exists, the build step is skipped and the existing artifact is reused.
- Your function comes up at
https://<name>.fn.sinch.com(orhttps://<name>.private.fn.sinch.comif deployed private). - If your project's
sinch.jsondeclares a Voice service or Conversation App, the CLI updates its webhook/callback URL to point at the new function.
- A caller dials your Sinch phone number.
- Sinch's voice platform receives the call and looks up the Voice service assigned to that number.
- The service sends a
call.incomingevent to your function'svoiceWebhookexport. - Your function runs, builds a commands response, returns it.
- Sinch runs the commands and controls the call accordingly — plays audio, runs menus, dials legs, records, etc.
- If a menu doesn't resolve every branch itself, Sinch sends a
call.menuevent when it completes. You respond with the next set of commands. - When the call ends, Sinch sends a
call.hangup(orcall.failed) event. Your function can log or clean up.
Functions still wired to a legacy Voice API v1 application receive the older ICE/ACE/PIE/DICE callbacks instead.
See voice-callbacks for the callback lifecycle in detail.
- A user sends an SMS or WhatsApp message to a number assigned to your Conversation App.
- The Conversation platform posts a webhook to your function's URL.
- Your function runs, decides what to do, and optionally sends a reply via
context.conversation.messages.send(...).
- The CLI starts your function locally — hot-reloading via
tsupwatch (Node) ordotnet watch(C#). - It opens a tunnel (Cloudflare Tunnel) that gives your local process a public URL.
- It optionally updates your Voice service's webhook URL to the tunnel URL so Sinch can reach your laptop directly.
- When a real call hits the number, Sinch calls your laptop, and you see requests in real time via
sinch functions logs. - Stop the dev server and the CLI reverts any callback URLs it touched.
This means you can test against real Sinch traffic from your laptop, with no deploy loop.
| Where | What | Who manages it |
|---|---|---|
| OS keychain | CLI login credentials — API keys for the Sinch platform | The CLI itself, via sinch auth login |
Your project's .env (Node.js) or appsettings.json (C#) | Function config: keys with a value are deployed as variables, empty keys are secrets | You; read by sinch functions dev and sinch functions deploy |
sinch.json → variables | Local-only values: sinch functions dev loads them, deploy does not send them | You |
OS keychain (sinch secrets add) or dotnet user-secrets | Production secrets (e.g., ElevenLabs API key, OpenAI key), filled into empty keys at deploy | You; deploy copies them to the platform, injected at runtime |
See configuration-secrets for the full story of how each layer is resolved.
Deployed functions run in an isolated, managed runtime environment. The platform provides:
- Cache — persistent and shared across invocations
- Blob storage — durable and backed up
- SQLite database — with automatic durable replication, so it survives restarts
- Per-function subdomain —
https://<name>.fn.sinch.com(https://<name>.private.fn.sinch.comwhen private) - Auto-scaling based on request volume
- Structured logging streamed via
sinch functions logs - Pre-initialized SDK clients for Voice, Conversation, SMS, Numbers (and Verification in C#) when the right environment variables are set
You don't see any of this infrastructure from your code. You write a handler, the runtime takes care of the rest.



