{"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":"c-runtime","__idx":0},"children":["C# runtime"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["It's ASP.NET MVC with controllers."]}," You extend a base controller, override the methods you care about, and dependency injection wires up services and SDK clients. If you already know ASP.NET, skim this page and you are basically done."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Package: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Sinch.Functions.Runtime"]}," (NuGet)"," ","Target framework: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".NET 9+"]}," ","Namespace: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SinchFunctions.Utils"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"project-structure","__idx":1},"children":["Project structure"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"MyFunction/\n├── MyFunction.csproj      ← references Sinch.Functions.Runtime\n├── FunctionController.cs  ← voice callbacks (extends SinchVoiceController)\n├── Init.cs                ← optional: ISinchFunctionInit for DI and extra routes\n├── appsettings.json       ← config (variables, not secrets)\n├── sinch.json             ← project manifest (name, runtime, variables)\n├── assets/                ← private files\n└── public/                ← static files, served at /\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There is no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Program.cs"]},". The runtime discovers your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ISinchFunctionInit"]}," and controllers automatically and boots the ASP.NET pipeline for you."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"hello-world","__idx":2},"children":["Hello world"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"// FunctionController.cs\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\nusing SinchFunctions.Models;\nusing SinchFunctions.Utils;\n\npublic class FunctionController : SinchVoiceController\n{\n    public FunctionController(\n        FunctionContext context,\n        IConfiguration configuration,\n        ILogger<FunctionController> logger)\n        : base(context, configuration, logger) { }\n\n    public override async Task<IActionResult> Ice(IceCallbackModel data)\n    {\n        return Ok(new IceSvamletBuilder()\n            .Instructions.Say(\"Hello from Sinch Functions!\")\n            .Action.Hangup()\n            .Build());\n    }\n\n    public override Task<IActionResult> Pie(PieCallbackModel data) => Task.FromResult<IActionResult>(Ok());\n    public override Task<IActionResult> Ace(AceCallbackModel data) => Task.FromResult<IActionResult>(Ok());\n    public override Task<IActionResult> Dice(DiceCallbackModel data) => Task.FromResult<IActionResult>(Ok());\n}\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["That's a complete, deployable voice function. All four abstract methods must be implemented; stub the ones you do not need with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Task.FromResult<IActionResult>(Ok())"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-four-base-controllers","__idx":3},"children":["The four base controllers"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Extend whichever matches your use case:"]},{"$$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":"Base class"},"children":["Base class"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Use for"},"children":["Use for"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SinchVoiceController"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Voice callbacks — override ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Ice"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Ace"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Pie"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Dice"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SinchConversationController"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Conversation API webhooks (SMS, WhatsApp, Messenger, etc.)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ElevenLabsController"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["ElevenLabs AI voice agent webhooks."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SinchController"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Plain ASP.NET controllers — your own REST endpoints."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can have multiple controllers in one project — a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["FunctionController : SinchVoiceController"]}," and a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["StatusController : SinchController"]}," side-by-side."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"custom-http-endpoints","__idx":4},"children":["Custom HTTP endpoints"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For REST endpoints alongside your voice logic, extend ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SinchController"]}," and write a regular MVC controller:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"[Route(\"api\")]\npublic class MyController : SinchController\n{\n    public MyController(FunctionContext context, IConfiguration config, ILogger<MyController> logger)\n        : base(context, config, logger) { }\n\n    [HttpGet(\"status\")]\n    public IActionResult GetStatus() => Ok(new { status = \"healthy\" });\n\n    [HttpPost(\"users\")]\n    public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest req)\n    {\n        await Context.Cache.Set($\"user:{req.Id}\", req, 3600);\n        return Ok(req);\n    }\n}\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Context"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Configuration"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Logger"]}," are all available on the base class. See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["add a custom endpoint"]}," for a worked example."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"conversation-webhooks","__idx":5},"children":["Conversation webhooks"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"public class MyConversationController : SinchConversationController\n{\n    public override async Task<IActionResult> MessageInbound(MessageInboundEvent callback)\n    {\n        var text = callback.GetText();\n        var channel = callback.GetChannel();  // \"SMS\", \"WHATSAPP\", etc.\n\n        if (text == \"hello\")\n        {\n            var reply = Reply(callback, \"Hi there!\");\n            await Context.Conversation!.Messages.Send(reply);\n        }\n        return Ok();\n    }\n}\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Helper extensions on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MessageInboundEvent"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using SinchFunctions.Utils;\n\ncallback.GetText();           // string?\ncallback.GetMedia();          // media message\ncallback.GetChannel();        // \"SMS\", \"WHATSAPP\", etc.\ncallback.GetContactId();\ncallback.GetConversationId();\ncallback.GetIdentity();       // sender's phone/PSID\ncallback.GetTo();             // your Sinch number\ncallback.IsTextMessage();\ncallback.IsMediaMessage();\ncallback.IsPostback();\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Reply(inbound, text)"]}," is a base-class helper that fills in the recipient and app_id from the inbound event so you don't have to."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"dependency-injection-via-isinchfunctioninit","__idx":6},"children":["Dependency injection via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ISinchFunctionInit"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ISinchFunctionInit"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Init.cs"]}," to register your own services, add HTTP clients, and map extra routes. The runtime discovers it automatically — no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Program.cs"]}," plumbing."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"public class FunctionInit : ISinchFunctionInit\n{\n    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)\n    {\n        services.AddScoped<ICustomerService, CustomerService>();\n        services.AddHttpClient<IMyApiClient, MyApiClient>(client =>\n        {\n            client.BaseAddress = new Uri(configuration[\"MY_API_URL\"] ?? \"\");\n        });\n    }\n\n    public void ConfigureApp(SinchWebApplication app)\n    {\n        app.LandingPageEnabled = true;\n        app.MapGet(\"/custom\", () => Results.Ok(new { status = \"ok\" }));\n    }\n}\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your middleware runs ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["after"]}," the platform middleware, so billing and logging cannot be bypassed."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-context-object","__idx":7},"children":["The context object"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["FunctionContext"]}," is injected into every controller constructor. It has cache, storage, database, logger, and pre-wired SDK clients:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"public override async Task<IActionResult> Ice(IceCallbackModel data)\n{\n    Logger.LogInformation(\"ICE from {Cli}\", data.Cli);\n\n    await Context.Cache.Set($\"call:{data.CallId}:cli\", data.Cli ?? \"unknown\", 3600);\n\n    // Pre-wired SDK clients\n    if (Context.Sms is not null)\n    {\n        await Context.Sms.Batches.Send(...);\n    }\n\n    return Ok(new IceSvamletBuilder()\n        .Instructions.Say(\"Welcome.\")\n        .Action.Hangup()\n        .Build());\n}\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["context object"]}," for the full tour, or ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/functions/reference/function-context"},"children":["function context reference"]}," for the Node + C# side-by-side cheat sheet."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"svaml-builders","__idx":8},"children":["SVAML builders"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The pattern is always: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Instructions.*"]}," → ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Action.*"]}," → ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Build()"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"// ICE\nreturn Ok(new IceSvamletBuilder()\n    .Instructions.Say(\"Welcome!\")\n    .Action.ConnectPstn(\"+15551234567\", cli: data.Cli)\n    .Build());\n\n// ACE — continue or hangup only\nreturn Ok(new AceSvamletBuilder().Action.Continue().Build());\n\n// PIE — respond to menu result\nreturn Ok(new PieSvamletBuilder()\n    .Instructions.Say(\"Connecting you to sales.\")\n    .Action.ConnectPstn(\"+15551111111\")\n    .Build());\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/functions/reference/svaml-cheatsheet"},"children":["SVAML cheat sheet"]}," for the most-used actions and ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["voice callbacks"]}," for the lifecycle."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"configuration-and-secrets","__idx":9},"children":["Configuration and secrets"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["IConfiguration"]}," is injected into your controllers by ASP.NET — no special API needed:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"var companyName = Configuration[\"COMPANY_NAME\"] ?? \"Acme Corp\";\nvar apiKey = Configuration[\"STRIPE_SECRET_KEY\"]\n    ?? throw new InvalidOperationException(\"STRIPE_SECRET_KEY is required\");\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["IConfiguration"]}," reads from environment variables (populated by the runtime from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sinch.json"]}," variables + platform secrets) and from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["appsettings.json"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["appsettings.Development.json"]}," if you have them."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["configuration & secrets"]}," for how the layers fit together."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"database","__idx":10},"children":["Database"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Context.Database.ConnectionString"]}," gives you a SQLite connection string. Bring your own SQLite library:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using Microsoft.Data.Sqlite;\n\nusing var conn = new SqliteConnection(Context.Database.ConnectionString);\nconn.Open();\nusing var cmd = conn.CreateCommand();\ncmd.CommandText = \"CREATE TABLE IF NOT EXISTS call_log (id INTEGER PRIMARY KEY, caller TEXT, ts INTEGER)\";\ncmd.ExecuteNonQuery();\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Or with Dapper:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using var conn = new SqliteConnection(Context.Database.ConnectionString);\nvar calls = await conn.QueryAsync<CallRecord>(\"SELECT * FROM call_log ORDER BY ts DESC LIMIT 10\");\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In production, the platform automatically replicates and backs up the database. Your code doesn't change."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"do-and-dont","__idx":11},"children":["Do and don't"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Do"]}," implement all four abstract methods on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SinchVoiceController"]},", even if some just return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Ok()"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Do"]}," use builder ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".Build()"]}," for SVAML responses — never return raw anonymous objects."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Do"]}," register your own services via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ISinchFunctionInit.ConfigureServices"]},", not by hand-rolling a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Program.cs"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Don't"]}," override ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HandleWebhook"]}," on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SinchVoiceController"]}," — the base class handles routing."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Don't"]}," use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AceSvamletBuilder"]}," to connect calls — ACE only supports ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Continue"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Hangup"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Don't"]}," return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," from any callback method — always return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Ok(...)"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"related","__idx":12},"children":["Related"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["Handlers concept"]}," — URL-to-controller mapping in detail"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["Context object concept"]}," — cache/storage/database/SDK clients"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/functions/reference/function-context"},"children":["Function context reference"]}," — side-by-side cheat sheet"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/functions/reference/svaml-cheatsheet"},"children":["SVAML cheat sheet"]}," — most-used actions"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"#"},"children":["Node.js runtime"]}," — the same thing for Node devs"]}]}]},"headings":[{"value":"C# runtime","id":"c-runtime","depth":1},{"value":"Project structure","id":"project-structure","depth":2},{"value":"Hello world","id":"hello-world","depth":2},{"value":"The four base controllers","id":"the-four-base-controllers","depth":2},{"value":"Custom HTTP endpoints","id":"custom-http-endpoints","depth":2},{"value":"Conversation webhooks","id":"conversation-webhooks","depth":2},{"value":"Dependency injection via ISinchFunctionInit","id":"dependency-injection-via-isinchfunctioninit","depth":2},{"value":"The context object","id":"the-context-object","depth":2},{"value":"SVAML builders","id":"svaml-builders","depth":2},{"value":"Configuration and secrets","id":"configuration-and-secrets","depth":2},{"value":"Database","id":"database","depth":2},{"value":"Do and don't","id":"do-and-dont","depth":2},{"value":"Related","id":"related","depth":2}],"frontmatter":{"seo":{"title":""}},"lastModified":"2026-04-15T14:23:23.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/functions/runtimes/csharp","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}