About functions
What OmniLab functions are, how they run, when to use one instead of a webhook, and the limits your code works within.
A function is a small piece of TypeScript that runs on OmniLab's own infrastructure. You write it in OmniLab Studio, OmniLab compiles it, and from then on the platform runs it for you — either when something happens in a campaign, or at a specific decision point such as a contact signing up.
You need no server, no endpoint, and no deployment of your own.
Functions are configured under Enterprise Settings, which needs an Admin role with the Global Organization selected — worth confirming you have that access before planning work, because the tab is hidden without it.
How it works
You store one TypeScript file with one exported handler. When you build it, OmniLab compiles that file to a WebAssembly module and keeps the compiled version. Every invocation from then on loads it into a fresh sandbox and calls your export.
import type { PlatformEventHandler } from "./omnilab";
export const onPlatformEvent: PlatformEventHandler = (event) => {
console.log("Something happened: " + event.type);
};Your handler reaches the outside world through the calls OmniLab provides: read a config value, read a secret, make an HTTP request to a host you have allow-listed, read a contact, and write contact custom fields. That set is deliberately small — it is what lets OmniLab run your code on its own infrastructure safely, and it is enough for most integration work.
Each invocation is independent. Nothing you set on one call carries over to the next, so anything you need to keep goes into contact custom fields or into your own system over HTTP.
Functions or webhooks?
Both let you react to what happens in OmniLab. The difference is where your code runs and whether it can change the outcome.
| Function | Webhook | |
|---|---|---|
| Runs on | OmniLab's infrastructure | Yours |
| You need to host | Nothing | An HTTPS endpoint |
| Can change what OmniLab does | Yes, for one kind | No |
| Best for | Logic that must run inside an OmniLab operation, or small integrations you would rather not host | Feeding a system you already run |
A webhook tells your systems that something happened, after OmniLab has already decided it. A function runs inside OmniLab and — for one of the two kinds — inside the decision itself, so it can reject a signup, normalize an email before it is stored, or send a verification message through your own provider.
Choose a kind
Every function is one of two kinds, and the kind decides when it runs and what it can affect. It is fixed when you create the function, so make this choice before you start writing.
| Platform event function | Hook function | |
|---|---|---|
| Runs | After something happened | During an operation, which waits for it |
| Can it change the outcome? | No | Yes — it continues or refuses |
| Your export | onPlatformEvent | on + the hook point, for example onContactCreatePre |
| Wired up by | A subscription to event types | A binding to a hook point |
| Your return value | Discarded | The decision OmniLab acts on |
| If it fails | The activity is unaffected | The operation is blocked |
Start with a platform event function when either would work. It cannot break anything a customer is doing, so the cost of getting it wrong is a missing side effect rather than a failed signup.
Changing the kind means starting again
The kind determines the export your code must provide, which tabs appear in the editor, and the payload the test runner sends. There is no way to convert one kind into another: you create a new function and delete the old one.
From draft to running
Saving and building are separate on purpose. Saving stores your code and type-checks it; it never changes what is running. Only a build produces a new compiled version.
The status in the function list is one of Draft, Building, Ready, Build failed, or Deleted. Only a Ready function runs.
The two paths out of Building are worth knowing. A function that has never built successfully goes to Build failed. A function that is already live and fails a rebuild returns to Ready and keeps serving the version it was serving before, so a broken rebuild never takes a working function offline.
What your code works within
| Language | TypeScript, one file, entry point user.ts |
| Time per invocation | Up to 2 seconds |
| Outbound calls per invocation | 20, counting every SDK call |
| Network | Only hosts you allow-list; a function with an empty list makes no outbound calls |
| Memory | 2 MiB by default, up to 4 MiB |
Two rules catch people out. Secrets never go in your source — you store them in a bundle, bind the bundle to the function, and read them by name at runtime. And contact custom field keys beginning system. or feature. are reserved, so writes to them are rejected.
Full values, error codes and contracts are in the SDK reference.