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.

4 min read

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.

user.ts — hello world
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:

What your handler receives
{
  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:

user.ts — write a contact custom field
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.

FieldWhat to enter
Display nameA label for this subscription, such as Loyalty award trigger.
Event typesThe events that should trigger the function. Leave empty to match every event.
FiltersOptional narrowing conditions, combined with AND and matched exactly.
StatusActive 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.

Subscription form with the event type picker, custom event field and filters

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 happenedWhat OmniLab does
Your handler throwsThe 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 sideRetried with increasing backoff, up to five attempts
A permanent problem, such as the function having been deletedStops 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.

On this page