Skip to content

Context object

Every handler receives a FunctionContext as its first argument (Node) or as an injected dependency (C#). Think of it as the standard library of the Sinch runtime — everything you need to interact with state, configuration, and the rest of Sinch is hanging off of it.

What's in it

Both runtimes expose the same capabilities with idiomatic names.

CapabilityNode.jsC#
Key-value cachecontext.cacheContext.Cache
Blob / file storagecontext.storageContext.Storage
SQLite databasecontext.database (path)Context.Database (conn)
Structured loggercontext.log / consoleContext.Logger
Project + function infocontext.configContext.Configuration
Env varscontext.envContext.Configuration
Request metadatacontext.requestId, .timestampAvailable via HttpContext
Pre-wired Voice SDKcontext.voiceContext.Voice
Pre-wired Conversation SDKcontext.conversationContext.Conversation
Pre-wired SMS SDKcontext.smsContext.Sms
Pre-wired Numbers SDKcontext.numbersContext.Numbers
Pre-wired Verification SDKContext.Verification
Read private assetscontext.assets(filename)Package-level helpers

The SDK client properties are null / undefined if the corresponding environment variables are not set — see configuration & secrets for which vars unlock which client.

Cache

Key-value store with TTL. Use it for per-call state (keyed by callid), rate limiting, session data, and anything you want to survive between callbacks without a full database trip.

  • Dev: in-memory. Lost on restart.
  • Prod: persistent, shared across invocations, distributed.
// Node
await context.cache.set('session:abc', { userId: 'u1' }, 1800);
const session = await context.cache.get<{ userId: string }>('session:abc');
await context.cache.extend('session:abc', 600);
await context.cache.delete('session:abc');
const keys = await context.cache.keys('session:*');
// C#
await Context.Cache.Set("session:abc", new { UserId = "u1" }, 1800);
var session = await Context.Cache.Get<MySession>("session:abc");
if (await Context.Cache.Exists("session:abc"))
    await Context.Cache.Delete("session:abc");

Default TTL is 3600 seconds. Values are JSON-serialized.

Storage

Blob storage for persistent files — call recordings, reports, user uploads, arbitrary data.

  • Dev: local filesystem in ./storage/.
  • Prod: durable cloud storage with a local read cache.
// Node
await context.storage.write('reports/daily.json', JSON.stringify(data));
const buf = await context.storage.read('reports/daily.json');
const files = await context.storage.list('reports/');
const exists = await context.storage.exists('reports/old.json');
await context.storage.delete('reports/old.json');
// C#
await Context.Storage.WriteAsync("reports/daily.json", JsonSerializer.Serialize(data));
var text = await Context.Storage.ReadTextAsync("reports/daily.json");
var files = await Context.Storage.ListAsync("reports/");

// Streaming for big files
using var stream = await Context.Storage.ReadStreamAsync("large-file.bin");

Keys can include path separators — treat them like folder paths.

Database

A per-function SQLite database. You bring your own SQLite library; the runtime manages the file location and replication.

  • Dev: plain local SQLite file in ./data/.
  • Prod: SQLite with automatic durable replication. The database survives restarts and scale events, with no code changes required.

Pure JavaScript SQLite compiled to WebAssembly. No native compilation.

import initSqlJs from 'sql.js';
import { readFileSync, writeFileSync, existsSync } from 'fs';

const SQL = await initSqlJs();
const buf = existsSync(context.database) ? readFileSync(context.database) : undefined;
const db = new SQL.Database(buf);

db.run('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)');
db.run('INSERT OR REPLACE INTO kv VALUES (?, ?)', ['greeting', 'hello']);

writeFileSync(context.database, Buffer.from(db.export()));
db.close();

better-sqlite3 is faster but needs native compilation — use it when you can guarantee the build environment has python3 / make / g++.

C# — Microsoft.Data.Sqlite

using var conn = new SqliteConnection(Context.Database.ConnectionString);
conn.Open();

using var cmd = conn.CreateCommand();
cmd.CommandText = "CREATE TABLE IF NOT EXISTS call_log (id INTEGER PRIMARY KEY, caller TEXT, ts INTEGER)";
cmd.ExecuteNonQuery();

Or with Dapper:

using var conn = new SqliteConnection(Context.Database.ConnectionString);
var calls = await conn.QueryAsync<CallRecord>("SELECT * FROM call_log ORDER BY ts DESC LIMIT 10");

Config and variables

context.config exposes project and function metadata — useful for logging, feature flags by environment, and constructing return URLs.

context.config.projectId       // your Sinch Project ID
context.config.functionName    // e.g. 'my-ivr'
context.config.environment     // 'development' | 'production'
context.config.variables       // object of sinch.json variables

For a richer API, use createConfig(context) (aka createUniversalConfig):

import { createConfig } from '@sinch/functions-runtime';

const config = createConfig(context);
const apiKey = config.requireSecret('STRIPE_SECRET_KEY'); // throws if missing
const company = config.getVariable('COMPANY_NAME', 'Acme Corp');
if (config.isProduction()) { /* ... */ }

In C#, use the injected IConfiguration:

var companyName = Configuration["COMPANY_NAME"] ?? "Acme Corp";
var apiKey = Configuration["STRIPE_SECRET_KEY"]
    ?? throw new InvalidOperationException("STRIPE_SECRET_KEY is required");

Pre-wired Sinch SDK clients

The runtime instantiates Sinch SDK clients for you and attaches them to the context. No credential management in your function.

ClientNodeC#Env vars required
Voicecontext.voiceContext.VoiceVOICE_APPLICATION_KEY, VOICE_APPLICATION_SECRET
Conversationcontext.conversationContext.ConversationCONVERSATION_APP_ID
SMScontext.smsContext.SmsSMS_SERVICE_PLAN_ID
Numberscontext.numbersContext.NumbersENABLE_NUMBERS_API=true
Verification (C#)Context.VerificationVERIFICATION_APPLICATION_ID, VERIFICATION_APPLICATION_SECRET

All clients require the base Project credentials: PROJECT_ID, PROJECT_ID_API_KEY, PROJECT_ID_API_SECRET.

If a required variable is missing, the property is null — always null-check before using:

if (context.conversation) {
  await context.conversation.messages.send({ ... });
}
if (Context.Conversation is not null)
    await Context.Conversation.Messages.Send(...);

Assets

Private files you ship alongside your code — prompts, JSON data, templates. Keep them in the assets/ directory and read them with context.assets(filename). Do not use fs.readFileSync('./assets/...') — the deployed artifact structure is different from your dev tree, and the runtime handles the translation for you.

const greetingText = await context.assets('greetings/en.txt');

For public static files (images, CSS, public JSON), use the public/ directory instead. The runtime serves it at /.

Logger

context.log (Node) and Context.Logger (C#) are structured loggers. In production, output is captured, indexed, and streamed via sinch functions logs.

context.log?.info('Call received', { callId: data.callid, cli: data.cli });
Logger.LogInformation("Call received from {Cli}", data.Cli);

In Node, console.log also works — it's captured the same way as context.log.

Request metadata

  • context.requestId — a tracing ID unique to this invocation. Include it in logs and when calling out to other services; it makes cross-service debugging bearable.
  • context.timestamp — ISO 8601 timestamp when the request entered the runtime.