Serverless Functions
Serverless Functions let you run your own TypeScript inside Cortex — triggered by domain events or invoked over HTTP — without standing up any infrastructure. They’re the right tool when you want custom logic close to your voice data: email a meeting summary, push a transcript to your CRM, escalate a toxic message, or expose a small HTTP endpoint.
You write the code in the dashboard editor (full IntelliSense), publish it, and Cortex transpiles, snapshots and deploys it to ODIN Fleet. Events are delivered to it automatically.
A function at a glance
A function exports an HTTP handler and any number of event handlers:
Named event handlers
Subscribe to a domain event simply by exporting a handler named after it. The rule:
split the event type on ., PascalCase each segment, and prefix on.
| Event | Handler export |
|---|---|
message.created | onMessageCreated |
participant.joined | onParticipantJoined |
gathering.member.joined | onGatheringMemberJoined |
session.annotation.created | onSessionAnnotationCreated |
Named handlers exist for the user-facing event domains: session, message,
participant, gathering, bot and sanction, plus the annotation events. Each handler’s
event argument is the domain event itself (id, type, source, tenantId,
projectId, resourceId, timestamp and an action-typed payload with action and data),
and it is fully typed, so your editor knows the shape of event.payload.data. Exporting the handler is all it takes — there’s no separate
subscription list to keep in sync.
For wildcard subscriptions (message.*, *) or events that have no named handler, export an
eventScopes array plus a generic eventHandler. A function can mix both styles: a matching
named handler wins, and eventHandler receives everything else it subscribed to.
ctx.cortex — a pre-authenticated client
Every handler receives a ctx whose ctx.cortex is a ready-to-use, project-scoped
Cortex SDK client. No keys, no setup — call the API straight away:
ctx.cortex is a project scope of the SDK — the same object you’d get from
client.project(projectId) — so it exposes sessions, participants, gatherings,
sanctions, plugins, webhooks, functions, settings and rooms. Manager methods return
live objects, so fields like session.title are right on the result.
The runtime authenticates ctx.cortex for you and keeps the underlying credential out of
reach of your code — you never handle a secret, and no API-key scopes apply. (Available on
runtime v2 and newer.)
Push into the voice room
On runtime v6, ctx.cortex.rooms.send() hands a message to the Cortex bot of a live
session, which delivers it to the players in that ODIN room — for example, to announce that a
lobby is ready without anyone polling:
The session must be live (its bot in the room). Message types starting with cortex. are
reserved, and the per-room limits apply (8 KB per message, 20 messages per second per room).
See Room Push for how clients receive these messages.
Dependencies & environment variables
Functions can declare npm dependencies and environment variables — configured per
project. Declared packages are available to import/require, and their types are loaded
into the editor so IntelliSense works.
Environment variables are injected into the runtime; read them with process.env. Mark
credentials with isSecret: true — secret values are encrypted at rest and never shown again
(the API returns them masked). updateEnvVars() replaces the whole list, so always send
every variable the project needs. Names must start with a letter or underscore and contain
only letters, digits and underscores. This is exactly what the
“email a meeting summary” guide uses
(node-mailjet + Mailjet credentials).
CORTEX_API_URL and CORTEX_PROJECT_SECRET are managed by the platform (they wire up
ctx.cortex) and are removed before your code runs, so Cortex rejects them as environment
variable names with a 400. Avoid the CORTEX_ prefix for your own variables — use
ctx.cortex to call Cortex instead.
The runtime image preinstalls @4players/odin-cortex and @4players/fleet, so you can
import them without declaring them as dependencies. ctx.cortex is already an authenticated
instance of the Cortex SDK.
The lifecycle: edit → publish → deploy
- Activate Functions for the project once — Cortex provisions the Fleet resources it needs.
- Create & edit a function in the dashboard. The editor type-checks your TypeScript and blocks publishing while there are errors.
- Publish — Cortex transpiles your TypeScript to JavaScript, snapshots it as an immutable version, and analyzes which events it subscribes to.
- Deploy the version to Fleet. Cortex records the running endpoint and wires up event delivery.
Invoking over HTTP
A deployed function is reachable at a per-project URL (returned as invokeUrl when you
deploy, and shown in the dashboard):
Any HTTP method works. The handler export receives the request — method, path (the sub
path after the slug, or /), query (the parsed query string), headers, body (parsed
JSON) and rawBody — and returns { statusCode, headers?, body }:
Each function has an auth mode that decides who may call it:
| Auth mode | How to call it |
|---|---|
public | No credential — anyone with the URL. |
api_key | Send an API key (X-API-Key: ots_live_... or Authorization: Bearer ots_live_...). The key must belong to your account, carry the functions scope, and — if it is bound to a project — be bound to the function’s project. |
signed | Send X-Cortex-Signature: sha256=<hex>, an HMAC-SHA256 of the raw request body keyed with the function’s OTS_SIGNING_SECRET environment variable. For another service you control. |
Runtime versions
The function runtime is published as immutable versions (v1, v2, …) and each
project is pinned to a specific one, so a platform update can never silently change your
function’s behavior. New projects start on the latest version. When a newer runtime is
available, the dashboard shows an upgrade option with its release notes; you upgrade when
you’re ready.
| Version | What it brings |
|---|---|
v1 | First pinned runtime; structured errors that point at the failing source line. |
v2 | Preinstalled @4players/odin-cortex and @4players/fleet SDKs and the pre-authenticated ctx.cortex. |
v3 | Breaking: event handlers receive the bare domain event, so data is at event.payload.data. |
v4, v5 | Do not use — these images fail to start. Upgrade to v6. |
v6 | Current. Preinstalled SDK updated to @4players/odin-cortex 1.10, adding ctx.cortex.rooms.send(). Breaking coming from v3: ctx.cortex.voice and ctx.cortex.users are gone, and response shapes follow the current API. From v4/v5 it is a pure fix. |
You can also check and upgrade from code:
Functions on a project pinned to v4 or v5 don’t run at all. Upgrade to v6 — the
upgrade restarts the runtime on the new image.
Errors & debugging
When a handler throws, the response (HTTP or event) includes the error message, a trimmed
stack, and a parsed location (file, line, column) pointing at the failing line in
your original TypeScript — so you can jump straight to the problem. console.log output is
captured per invocation.