{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/functions/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["partial"]},"type":"markdown"},"seo":{"title":"Developer Documentation","siteUrl":"https://developers.sinch.com","llmstxt":{"title":"Sinch Developer Documentation","description":"LLMs.txt containing a map of all the documentation files for Sinch.","sections":[{"title":"SMS API","description":"The SMS API allows you to send and receive SMS messages with a few easy steps. You can also send bulk SMS messages to multiple customers using the Sinch SMS service.","includeFiles":["docs/sms/**/*.md","docs/sms/**/*.yaml"],"excludeFiles":["docs/sms/index.md"]},{"title":"Numbers API","description":"The Numbers API enables you to search for, view, and activate numbers. It's considered a precursor to other APIs in the Sinch product family. The numbers API can be used in tandem with any of our APIs that perform messaging or calling.","includeFiles":["docs/numbers/**/*.md","docs/numbers/**/*.yaml"],"excludeFiles":["docs/numbers/index.md"]},{"title":"Conversation API","description":"Send and receive messages globally on many popular channels with ease and confidence when using Sinch's Conversation API. Conversation API is the preferred API for sending mobile messages on SMS and other social channels with Sinch. It is a simple API with unified error messages, consistent request payloads, and common webhook payloads that are channel-agnostic.","includeFiles":["docs/conversation/**/*.md","docs/conversation/**/*.yaml"],"excludeFiles":["docs/conversation/index.md"]},{"title":"Voice API","description":"The Voice API works as a big telephony switch. The Voice API handles incoming phone calls (also known as incoming call “legs”), sets up outgoing phone calls (or outgoing call “legs”), and bridges the two. The incoming call leg may come in over a data connection (from a smartphone or web application using the Sinch SDKs) or through a local phone number (from the PSTN network). Similarly, the outgoing call leg can be over data (to another smartphone or web application using the Sinch SDKs) or the PSTN network.","includeFiles":["docs/voice/**/*.md","docs/voice/**/*.yaml"],"excludeFiles":["docs/voice/index.md"]},{"title":"Verification API","description":"The Verification API is a platform for phone number verification. It consists of the API and different software development kits (the Sinch SDKs) that you integrate with your smartphone or web application and cloud based back-end services. Together they enable SMS, Flashcall, Phone Call and Data verification in your application.","includeFiles":["docs/verification/**/*.md","docs/verification/**/*.yaml"],"excludeFiles":["docs/verification/index.md"]},{"title":"Provisioning API","description":"Provisioning API allows you to programmatically set up your senders, accounts and templates on your favorite messaging platforms on the Conversation API. For now, you can create your first WhatsApp channel through Meta's Embedded sign up, you can configure your first SMS App and configure your webhooks. As development continues, we will be adding the most commonly used channels.","includeFiles":["docs/provisioning-api/**/*.md","docs/provisioning-api/**/*.json"],"excludeFiles":["docs/provisioning-api/index.md"]},{"title":"Elastic SIP Trunking API","description":"With Elastic SIP Trunking you can create and manage your SIP trunks and phone numbers programmatically.","includeFiles":["docs/est/**/*.md","docs/est/**/*.yaml"],"excludeFiles":["docs/est/index.md"]},{"title":"Fax API","description":"Send and receive HIPAA compliant faxes on our modern fax platform using our developer-friendly API.","includeFiles":["docs/fax/**/*.md","docs/fax/**/*.yaml"],"excludeFiles":["docs/fax/index.md"]},{"title":"In-app Voice and Video SDK","description":"The In-app Voice and Video SDK enables you to add voice and video calling capabilities directly into your mobile or web application using the Sinch SDKs.","includeFiles":["docs/in-app-calling/**/*.md"],"excludeFiles":["docs/in-app-calling/index.md"]}],"hide":false,"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"build-call-routing","__idx":0},"children":["Build Call Routing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Route inbound calls based on business hours, caller ID, geography, or custom logic. All routing decisions happen in your ICE or PIE handler and produce a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connectPstn"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connectSip"]}," action."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-from-template","__idx":1},"children":["Create from template"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"sinch functions init call-forwarding --name my-router\ncd my-router\nsinch functions dev\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"time-based-routing","__idx":2},"children":["Time-based routing"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"Node.js","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import type { VoiceFunction, FunctionContext } from '@sinch/functions-runtime';\nimport type { Voice } from '@sinch/voice';\nimport { createIceBuilder, createUniversalConfig } from '@sinch/functions-runtime';\n\nfunction isBusinessHours(timezone = 'America/New_York'): boolean {\n  const now = new Date();\n  const local = new Date(now.toLocaleString('en-US', { timeZone: timezone }));\n  const day = local.getDay();\n  const hour = local.getHours();\n  return day >= 1 && day <= 5 && hour >= 9 && hour < 17;\n}\n\nexport default {\n  async ice(context: FunctionContext, event: Voice.IceRequest) {\n    const config = createUniversalConfig(context);\n    const officeNumber = config.requireVariable('OFFICE_NUMBER');\n    const voicemailNumber = config.requireVariable('VOICEMAIL_NUMBER');\n\n    if (isBusinessHours()) {\n      return createIceBuilder()\n        .say('Connecting you to our team.')\n        .connectPstn(officeNumber)\n        .build();\n    } else {\n      return createIceBuilder()\n        .say(\n          'Our office is currently closed. We are open Monday through Friday, 9 AM to 5 PM Eastern.'\n        )\n        .connectPstn(voicemailNumber)\n        .build();\n    }\n  },\n} satisfies VoiceFunction;\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using Microsoft.AspNetCore.Mvc;\nusing SinchFunctions.Models;\nusing SinchFunctions.Utils;\n\nnamespace SinchFunctions\n{\n    public class FunctionController : SinchVoiceController\n    {\n        public FunctionController(\n            FunctionContext context, IConfiguration configuration,\n            ILogger<FunctionController> logger) : base(context, configuration, logger) { }\n\n        public override async Task<IActionResult> Ice([FromBody] IceCallbackModel callbackData)\n        {\n            var officeNumber = Configuration[\"OFFICE_NUMBER\"]\n                ?? throw new InvalidOperationException(\"OFFICE_NUMBER is required\");\n            var voicemailNumber = Configuration[\"VOICEMAIL_NUMBER\"]\n                ?? throw new InvalidOperationException(\"VOICEMAIL_NUMBER is required\");\n\n            if (IsBusinessHours())\n            {\n                return Ok(new IceSvamletBuilder()\n                    .Instructions.Say(\"Connecting you to our team.\")\n                    .Action.ConnectPstn(officeNumber)\n                    .Build());\n            }\n\n            return Ok(new IceSvamletBuilder()\n                .Instructions.Say(\"Our office is currently closed. We are open Monday through Friday, 9 AM to 5 PM Eastern.\")\n                .Action.ConnectPstn(voicemailNumber)\n                .Build());\n        }\n\n        private static bool IsBusinessHours(string timezone = \"Eastern Standard Time\")\n        {\n            var tz = TimeZoneInfo.FindSystemTimeZoneById(timezone);\n            var local = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);\n            return local.DayOfWeek >= DayOfWeek.Monday\n                && local.DayOfWeek <= DayOfWeek.Friday\n                && local.Hour >= 9 && local.Hour < 17;\n        }\n    }\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"caller-id-based-routing","__idx":3},"children":["Caller ID-based routing"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"Node.js","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"const VIP_LIST = new Set(['+15550001111', '+15550002222']);\nconst BLOCKED_LIST = new Set(['+19990001111']);\n\nexport default {\n  async ice(context: FunctionContext, event: Voice.IceRequest) {\n    const caller = event.cli ?? '';\n\n    if (BLOCKED_LIST.has(caller)) {\n      return createIceBuilder().say('We are unable to take your call. Goodbye.').hangup().build();\n    }\n\n    if (VIP_LIST.has(caller)) {\n      return createIceBuilder()\n        .say('Welcome. Connecting you to our priority line.')\n        .connectPstn('+15551110010', { cli: caller })\n        .build();\n    }\n\n    return createIceBuilder()\n      .say('Thank you for calling. Connecting you now.')\n      .connectPstn('+15551110001')\n      .build();\n  },\n} satisfies VoiceFunction;\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"private static readonly HashSet<string> VipList = new() { \"+15550001111\", \"+15550002222\" };\nprivate static readonly HashSet<string> BlockedList = new() { \"+19990001111\" };\n\npublic override async Task<IActionResult> Ice([FromBody] IceCallbackModel callbackData)\n{\n    var caller = callbackData.Cli ?? \"\";\n\n    if (BlockedList.Contains(caller))\n        return Ok(new IceSvamletBuilder()\n            .Instructions.Say(\"We are unable to take your call. Goodbye.\")\n            .Action.Hangup().Build());\n\n    if (VipList.Contains(caller))\n        return Ok(new IceSvamletBuilder()\n            .Instructions.Say(\"Welcome. Connecting you to our priority line.\")\n            .Action.ConnectPstn(\"+15551110010\", cli: caller).Build());\n\n    return Ok(new IceSvamletBuilder()\n        .Instructions.Say(\"Thank you for calling. Connecting you now.\")\n        .Action.ConnectPstn(\"+15551110001\").Build());\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"geographic-routing","__idx":4},"children":["Geographic routing"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"Node.js","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"function getRegion(number: string): 'us' | 'uk' | 'eu' | 'default' {\n  if (number.startsWith('+1')) return 'us';\n  if (number.startsWith('+44')) return 'uk';\n  if (number.startsWith('+3') || number.startsWith('+4')) return 'eu';\n  return 'default';\n}\n\nconst REGIONAL_NUMBERS: Record<string, string> = {\n  us: '+15551110001',\n  uk: '+441110000001',\n  eu: '+33110000001',\n  default: '+15551110001',\n};\n\nexport default {\n  async ice(context: FunctionContext, event: Voice.IceRequest) {\n    const region = getRegion(event.cli ?? '');\n    return createIceBuilder()\n      .say('Connecting you to your regional support team.')\n      .connectPstn(REGIONAL_NUMBERS[region])\n      .build();\n  },\n} satisfies VoiceFunction;\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"private static string GetRegion(string number) => number switch\n{\n    var n when n.StartsWith(\"+1\")  => \"us\",\n    var n when n.StartsWith(\"+44\") => \"uk\",\n    var n when n.StartsWith(\"+3\") || n.StartsWith(\"+4\") => \"eu\",\n    _ => \"default\"\n};\n\nprivate static readonly Dictionary<string, string> RegionalNumbers = new()\n{\n    [\"us\"] = \"+15551110001\", [\"uk\"] = \"+441110000001\",\n    [\"eu\"] = \"+33110000001\", [\"default\"] = \"+15551110001\",\n};\n\npublic override async Task<IActionResult> Ice([FromBody] IceCallbackModel callbackData)\n{\n    var region = GetRegion(callbackData.Cli ?? \"\");\n    return Ok(new IceSvamletBuilder()\n        .Instructions.Say(\"Connecting you to your regional support team.\")\n        .Action.ConnectPstn(RegionalNumbers[region]).Build());\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"connectpstn-options","__idx":5},"children":["ConnectPstn options"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"Node.js","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"return createIceBuilder()\n  .connectPstn('+15551234567', {\n    cli: '+15550000000', // Caller ID shown to recipient\n    maxDuration: 3600, // Max call length in seconds\n    timeout: 30, // Ring timeout\n    enableAce: true, // Receive ACE callback when answered\n    enablePie: true, // Receive PIE callback for DTMF\n    enableDice: true, // Receive DICE callback on disconnect\n    indications: 'us', // Ring tone country\n  })\n  .build();\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"return Ok(new IceSvamletBuilder()\n    .Action.ConnectPstn(\"+15551234567\", new ConnectPstnOptions\n    {\n        Cli = \"+15550000000\",\n        MaxDuration = 3600,\n        ConnectTimeout = 30,\n        Indications = \"us\",\n    })\n    .Build());\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"sip-routing","__idx":6},"children":["SIP routing"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"Node.js","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"return createIceBuilder()\n  .say('Connecting via SIP.')\n  .connectSip('sip:support@pbx.example.com', {\n    cli: event.cli,\n    headers: { 'X-Custom-Header': 'value' },\n  })\n  .build();\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"return Ok(new IceSvamletBuilder()\n    .Instructions.Say(\"Connecting via SIP.\")\n    .Action.ConnectSip(\"sip:support@pbx.example.com\", new ConnectSipOptions\n    {\n        Cli = callbackData.Cli,\n        Headers = new Dictionary<string, string> { [\"X-Custom-Header\"] = \"value\" }\n    })\n    .Build());\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"fallback-handling","__idx":7},"children":["Fallback handling"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"Node.js","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"export default {\n  async ice(context: FunctionContext, event: Voice.IceRequest) {\n    return createIceBuilder()\n      .connectPstn('+15551110001', { enableAce: true, enableDice: true, timeout: 20 })\n      .build();\n  },\n\n  async dice(context: FunctionContext, event: Voice.DiceRequest) {\n    if (event.reason === 'noAnswer' || event.reason === 'busy') {\n      // DICE fires after disconnect — re-routing is not possible here.\n      // Use an IVR menu or park action for active fallback.\n      console.warn('Primary destination did not answer:', event.reason);\n    }\n  },\n} satisfies VoiceFunction;\n","lang":"typescript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"public override async Task<IActionResult> Ice([FromBody] IceCallbackModel callbackData)\n{\n    return Ok(new IceSvamletBuilder()\n        .Action.ConnectPstn(\"+15551110001\", new ConnectPstnOptions { ConnectTimeout = 20 })\n        .Build());\n}\n\npublic override async Task<IActionResult> Dice([FromBody] DiceCallbackModel callbackData)\n{\n    if (callbackData.Reason == \"noAnswer\" || callbackData.Reason == \"busy\")\n        Logger.LogWarning(\"Primary destination did not answer: {Reason}\", callbackData.Reason);\n    return Ok();\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For sinch.json configuration and environment variables, see ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["Templates"]}," and ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["Configuration & Secrets"]},"."]}]},"headings":[{"value":"Build Call Routing","id":"build-call-routing","depth":1},{"value":"Create from template","id":"create-from-template","depth":2},{"value":"Time-based routing","id":"time-based-routing","depth":2},{"value":"Caller ID-based routing","id":"caller-id-based-routing","depth":2},{"value":"Geographic routing","id":"geographic-routing","depth":2},{"value":"ConnectPstn options","id":"connectpstn-options","depth":2},{"value":"SIP routing","id":"sip-routing","depth":2},{"value":"Fallback handling","id":"fallback-handling","depth":2}],"frontmatter":{"seo":{"title":""}},"lastModified":"2026-04-15T14:23:23.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/functions/guides/route-calls","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}