{"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-an-ai-voice-agent","__idx":0},"children":["Build an AI Voice Agent"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Connect inbound calls to an AI voice agent powered by ElevenLabs Conversational AI. The runtime handles SIP bridging; you write the webhook logic that personalizes each conversation."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-it-works","__idx":1},"children":["How it works"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Caller dials your Sinch number."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Sinch sends an ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ICE"]}," callback to your function."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your function responds with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connectAgent"]}," action targeting ElevenLabs via SIP."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Sinch bridges the call to ElevenLabs."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["(Optional) ElevenLabs sends a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["conversation-init"]}," webhook to personalize the greeting."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["After the call ends, ElevenLabs sends a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["post-call"]}," webhook with transcript and analysis."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"required-environment-variables","__idx":2},"children":["Required environment variables"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"ELEVENLABS_AGENT_ID=your-agent-id\nELEVENLABS_API_KEY=your-api-key    # for outbound calls and post-call webhooks\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"inbound-call--connect-to-agent","__idx":3},"children":["Inbound call — connect to agent"]},{"$$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, AgentProvider, createUniversalConfig } from '@sinch/functions-runtime';\n\nexport default {\n  async ice(context: FunctionContext, event: Voice.IceRequest) {\n    const config = createUniversalConfig(context);\n    const agentId = config.requireSecret('ELEVENLABS_AGENT_ID');\n\n    return createIceBuilder()\n      .say('Connecting you to our AI assistant. Please hold.')\n      .connectAgent(AgentProvider.ElevenLabs, agentId)\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":"using Microsoft.AspNetCore.Mvc;\nusing SinchFunctions.AI;\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 agentId = Configuration[\"ELEVENLABS_AGENT_ID\"]\n                ?? throw new InvalidOperationException(\"ELEVENLABS_AGENT_ID is required\");\n\n            return Ok(new IceSvamletBuilder()\n                .Instructions.Say(\"Connecting you to our AI assistant. Please hold.\")\n                .Action.ConnectAgent(AgentProvider.ElevenLabs, agentId)\n                .Build());\n        }\n\n        public override async Task<IActionResult> Ace([FromBody] AceCallbackModel callbackData)\n            => Ok(new AceSvamletBuilder().Action.Continue().Build());\n\n        public override async Task<IActionResult> Dice([FromBody] DiceCallbackModel callbackData)\n        {\n            Logger.LogInformation(\"Call ended: {Reason}, {Duration}s\",\n                callbackData.Reason, callbackData.Duration);\n            return Ok();\n        }\n    }\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"connectagent-options","__idx":4},"children":["ConnectAgent 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  .connectAgent(AgentProvider.ElevenLabs, agentId, {\n    cli: event.cli, // Pass caller ID to agent SIP trunk\n    maxDuration: 1800, // 30-minute max\n    suppressCallbacks: false, // Still receive ACE/DICE callbacks\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.ConnectAgent(AgentProvider.ElevenLabs, agentId, new ConnectAgentOptions\n    {\n        Cli = callbackData.Cli,\n        MaxDuration = 1800,\n        SuppressCallbacks = false,\n    })\n    .Build());\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"ivr-gating-before-agent-connection","__idx":5},"children":["IVR gating before agent connection"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Show a menu first and connect to the agent only if the caller opts in:"]},{"$$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 {\n  createIceBuilder,\n  createPieBuilder,\n  MenuBuilder,\n  AgentProvider,\n  createUniversalConfig,\n} from '@sinch/functions-runtime';\n\nexport default {\n  async ice(context: FunctionContext, event: Voice.IceRequest) {\n    const menu = new MenuBuilder()\n      .prompt('Press 1 to speak with our AI assistant, or press 0 to reach a human agent.')\n      .option('1', 'return(ai_agent)')\n      .option('0', 'return(human_agent)')\n      .timeout(5000)\n      .repeats(2)\n      .build();\n\n    return createIceBuilder().runMenu(menu).build();\n  },\n\n  async pie(context: FunctionContext, event: Voice.PieRequest) {\n    const config = createUniversalConfig(context);\n    const agentId = config.requireSecret('ELEVENLABS_AGENT_ID');\n    const humanNumber = config.requireVariable('HUMAN_AGENT_NUMBER');\n\n    if (event.menuResult?.value === 'ai_agent') {\n      return createPieBuilder()\n        .say('Connecting you to our AI assistant.')\n        .connectAgent(AgentProvider.ElevenLabs, agentId)\n        .build();\n    }\n\n    if (event.menuResult?.value === 'human_agent') {\n      return createPieBuilder()\n        .say('Connecting you to a human agent.')\n        .connectPstn(humanNumber)\n        .build();\n    }\n\n    return createPieBuilder().say('Invalid selection. Goodbye.').hangup().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":"public override async Task<IActionResult> Ice([FromBody] IceCallbackModel callbackData)\n{\n    var menu = MenuFactory.CreateMenu()\n        .Prompt(\"Press 1 to speak with our AI assistant, or press 0 to reach a human agent.\")\n        .Option(\"1\", \"return(ai_agent)\")\n        .Option(\"0\", \"return(human_agent)\")\n        .Timeout(5000).Repeats(2).Build();\n\n    return Ok(new IceSvamletBuilder().Action.RunMenu(menu).Build());\n}\n\npublic override async Task<IActionResult> Pie([FromBody] PieCallbackModel callbackData)\n{\n    var agentId = Configuration[\"ELEVENLABS_AGENT_ID\"]!;\n    var humanNumber = Configuration[\"HUMAN_AGENT_NUMBER\"]!;\n\n    return callbackData.MenuResult?.Value switch\n    {\n        \"ai_agent\" => Ok(new PieSvamletBuilder()\n            .Instructions.Say(\"Connecting you to our AI assistant.\")\n            .Action.ConnectAgent(AgentProvider.ElevenLabs, agentId).Build()),\n        \"human_agent\" => Ok(new PieSvamletBuilder()\n            .Instructions.Say(\"Connecting you to a human agent.\")\n            .Action.ConnectPstn(humanNumber).Build()),\n        _ => Ok(new PieSvamletBuilder()\n            .Instructions.Say(\"Invalid selection. Goodbye.\")\n            .Action.Hangup().Build()),\n    };\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"conversation-init-webhook","__idx":6},"children":["Conversation-init webhook"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["ElevenLabs calls this webhook before the agent's first word. Return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dynamic_variables"]}," to personalize the session."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure the URL in your ElevenLabs agent settings:"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://func-{project}-{name}.functions.sinch.com/webhook/elevenlabs/conversation-init"]}]},{"$$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 {\n  ElevenLabsController,\n  type ConversationInitRequest,\n  type ConversationInitResponse,\n} from '@sinch/functions-runtime';\n\nclass MyElevenLabsHandler extends ElevenLabsController {\n  async handleConversationInit(\n    request: ConversationInitRequest\n  ): Promise<ConversationInitResponse> {\n    const callerId = request.caller_id ?? 'unknown';\n    return {\n      dynamic_variables: {\n        caller_phone: callerId,\n        customer_name: 'valued customer',\n        account_tier: 'standard',\n      },\n    };\n  }\n\n  async handlePostCall(webhook: ElevenLabsPostCallWebhook): Promise<void> {\n    const { conversation_id, status, metadata, analysis } = webhook.data;\n    console.log('Call completed', {\n      conversationId: conversation_id,\n      duration: metadata.call_duration_secs,\n      summary: analysis?.transcript_summary,\n    });\n  }\n}\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.AI.ElevenLabs;\nusing SinchFunctions.Controllers;\n\nnamespace SinchFunctions\n{\n    public class MyElevenLabsWebhooksController : ElevenLabsController\n    {\n        private readonly ILogger<MyElevenLabsWebhooksController> _logger;\n\n        public MyElevenLabsWebhooksController(ILogger<MyElevenLabsWebhooksController> logger)\n        { _logger = logger; }\n\n        public override async Task<IActionResult> HandleConversationInitWebhook(\n            [FromBody] ConversationInitRequest request)\n        {\n            return Ok(new ConversationInitResponse\n            {\n                Type = \"conversation_initiation_client_data\",\n                DynamicVariables = new Dictionary<string, object>\n                {\n                    [\"caller_phone\"]   = request.CallerId ?? \"unknown\",\n                    [\"customer_name\"]  = \"valued customer\",\n                    [\"account_tier\"]   = \"standard\",\n                }\n            });\n        }\n\n        public override async Task<IActionResult> HandlePostCallWebhook(\n            [FromBody] ElevenLabsPostCallWebhook webhook)\n        {\n            _logger.LogInformation(\"Call completed: Id={Id}, Duration={Duration}s\",\n                webhook.Data?.ConversationId, webhook.Data?.Metadata?.CallDurationSecs);\n            return Ok();\n        }\n    }\n}\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"conversationinitresponse-fields","__idx":7},"children":["ConversationInitResponse fields"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dynamic_variables"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Record<string, string>"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Key-value pairs injected into agent prompt and first message"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["conversation_config_override.agent.first_message"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Override opening line"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["conversation_config_override.agent.prompt"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Override system prompt"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["conversation_config_override.agent.language"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["string"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Override language (e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"es\""]},")"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"outbound-calls-via-elevenlabs","__idx":8},"children":["Outbound calls via ElevenLabs"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["ElevenLabs initiates the outbound call. Your function bridges the SIP leg to PSTN:"]},{"$$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    if (event.originationType === 'SIP') {\n      const destination = event.to?.endpoint ?? '';\n      return createIceBuilder().connectPstn(destination, { cli: event.cli }).build();\n    }\n    return createIceBuilder().hangup().build();\n  },\n\n  async ace(context: FunctionContext, event: Voice.AceRequest) {\n    return new AceSvamlBuilder().continue().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":"public override async Task<IActionResult> Ice([FromBody] IceCallbackModel callbackData)\n{\n    if (callbackData.OriginationType == \"SIP\")\n    {\n        var destination = callbackData.To?.Endpoint ?? \"\";\n        return Ok(new IceSvamletBuilder()\n            .Action.ConnectPstn(destination, cli: callbackData.Cli).Build());\n    }\n    return Ok(new IceSvamletBuilder().Action.Hangup().Build());\n}\n\npublic override async Task<IActionResult> Ace([FromBody] AceCallbackModel callbackData)\n    => Ok(new AceSvamletBuilder().Action.Continue().Build());\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 an AI Voice Agent","id":"build-an-ai-voice-agent","depth":1},{"value":"How it works","id":"how-it-works","depth":2},{"value":"Required environment variables","id":"required-environment-variables","depth":2},{"value":"Inbound call — connect to agent","id":"inbound-call--connect-to-agent","depth":2},{"value":"ConnectAgent options","id":"connectagent-options","depth":2},{"value":"IVR gating before agent connection","id":"ivr-gating-before-agent-connection","depth":2},{"value":"Conversation-init webhook","id":"conversation-init-webhook","depth":2},{"value":"ConversationInitResponse fields","id":"conversationinitresponse-fields","depth":2},{"value":"Outbound calls via ElevenLabs","id":"outbound-calls-via-elevenlabs","depth":2}],"frontmatter":{"seo":{"title":""}},"lastModified":"2026-04-15T14:23:23.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/functions/guides/build-an-ai-voice-agent","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}