Platform event functions
Write a function that runs asynchronously after something happens in OmniLab, and subscribe it to the event types it should react to.
A platform event function runs after the fact. Something happens in OmniLab — a touchpoint is completed, a reward is won, a contact is created — and your function is invoked with that event. It cannot change what already happened, which makes it the safe kind to start with.
When to use it
Choose a platform event function when you want to react to activity: enrich a contact after they play, push a record to your CRM, notify an internal channel when a prize is claimed. If instead you need to influence an operation while it is happening — reject a signup, rewrite a submitted email — you need a hook function.
The handler
Your file exports onPlatformEvent. It receives one event and returns nothing.
import type { PlatformEventHandler } from "./omnilab";
export const onPlatformEvent: PlatformEventHandler = (event) => {
console.log("Hello world! Received " + event.type + " (" + event.id + ")");
};Build this, subscribe it to an event type, and every matching event shows up in the execution log with your line in the Logs panel. That is the whole loop, and it is worth confirming before you write anything more involved.
The event you receive always has the same five fields:
{
type: "touchpoint.completed.v1",
id: "evt-...", // unique per event
time: "2026-05-28T14:48:31Z",
source: "omnilab",
data: { /* the event payload */ }
}Doing something useful
Because the return value is discarded, a platform event function has an effect only by calling out through the SDK. This one tags the contact with the last touchpoint they finished:
import { OmniLab } from "./omnilab";
import type { PlatformEventHandler } from "./omnilab";
type TouchpointCompleted = {
contact?: { contact_id?: string };
touchpoint_title?: string;
};
export const onPlatformEvent: PlatformEventHandler = async (event) => {
const data = event.data as TouchpointCompleted;
const contactId = data.contact?.contact_id;
if (!contactId) {
console.log("No contact on " + event.id + ", nothing to do");
return;
}
await OmniLab.Contact.setCustomField(
contactId,
"last_touchpoint",
data.touchpoint_title ?? "unknown",
);
};event.data is the raw event payload, so cast it to the shape you expect and read defensively — fields differ between event types. The event payload reference lists what each family carries, and Load example on the Test tab gives you a complete, current sample for any type to develop against.
Handle the same event arriving twice
Delivery is at-least-once, so the same event id can reach your handler more than once. Setting a contact field to the same value twice is harmless. Two things are not: calling an external API that charges money, sends a message, or allocates a voucher — and appending to a field you just read, which can silently lose one of the two writes. Deduplicate on event.id before either.
Subscribe it to events
A built function is not triggered by anything until you subscribe it. Open the function and use the Subscriptions tab, then select Add subscription.
| Field | What to enter |
|---|---|
| Display name | A label for this subscription, such as Loyalty award trigger. |
| Event types | The events that should trigger the function. Leave empty to match every event. |
| Filters | Optional narrowing conditions, combined with AND and matched exactly. |
| Status | Active or Inactive. Only active subscriptions deliver. |
Events are grouped in the picker by family — Touchpoint, Reward, Contact, and Other — and you can search within it. If the platform emits an event type that is not in the picker, type it into the custom event field and select Add custom. See the event type list for everything available.
Filters narrow delivery to a specific Tenant, Group, Interaction, or Contact. With no filters, every event of the selected types triggers the function.

Select Create. The subscription appears in the list with its status, its event-type chips, and its filters.
A function can carry several subscriptions, but a tenant can have at most 5 active subscriptions per event type. The tab warns you when a type is at its cap.
Editing a subscription's status is not yet supported
The Status field appears in the edit form, but saving a status change has no effect and the interface tells you so. To stop a subscription delivering today, delete it and create a new one when you need it back.
What triggers a function, and what does not
Two platform rules apply on top of whatever you subscribe to.
Only events carrying an identified contact trigger a function. An event with no contact is dropped, so a subscription that looks correct can still appear to do nothing if the activity behind it was anonymous.
Anonymous page-visit events are never available. touchpoint.page_visit.v1 carries far more volume than every other event type, so functions are not permitted to run on it. Typing it into the custom event field is refused.
Failure and retries
Your function's failures do not affect the activity that produced the event — the operation has already completed by the time you are invoked.
| What happened | What OmniLab does |
|---|---|
| Your handler throws | The failure is recorded as an execution with a non-zero exit code, and is not retried — the same code would throw again |
| A transient problem on OmniLab's side | Retried with increasing backoff, up to five attempts |
| A permanent problem, such as the function having been deleted | Stops being retried, because retrying cannot help |
If something's blocked
The Subscriptions tab is missing. The tab only appears on platform event functions. Check the kind badge at the top of the editor.
A subscription exists but the function never runs. Confirm the function status is Ready — a Draft function is skipped entirely — and that the subscription is Active. Then check that your filters are not excluding everything, and that the activity you expect actually involves an identified contact.
The custom event type is rejected. Either the platform does not emit it, or it is the anonymous page-visit event, which functions can never subscribe to.
It ran, but nothing changed. Remember the return value is discarded. Check the Logs and the SDK call results in the execution detail — an EGRESS_BLOCKED or NOT_FOUND error there is the usual reason a handler runs cleanly but has no effect.