SalesOS

Custom events

Define your own platform events with a versioned payload schema, publish them from the API, functions, flows and workflows, and subscribe with webhooks, functions, flows, the CLI and GraphQL.

Catalog events tell you what happened to SalesOS records. Custom events are your own: an org admin defines a type such as order_shipped, published as custom.order_shipped, with a JSON Schema for its payload. Anything that can listen to catalog events can listen to yours.

Manage types under Settings → Developer → Custom events, or with /v1/custom-event-types (org owners and admins, session only; part of API access).

curl -X POST https://api.salesos.org/v1/custom-event-types \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "apiName": "order_shipped",
    "label": "Order shipped",
    "schema": {
      "type": "object",
      "properties": {
        "orderId": { "type": "string" },
        "carrier": { "type": "string", "enum": ["UPS", "DHL"] },
        "weightKg": { "type": "number" }
      },
      "required": ["orderId"],
      "additionalProperties": false
    },
    "publishAccess": "ADMINS"
  }'

The schema uses the same subset as custom-action inputs: an object of string, number, integer, boolean, date, date-time, email, URI, enum and string-list properties. Changing the schema creates the next version; every event carries the version its payload was validated against in the envelope's version, and GET /v1/custom-event-types/:id lists the history.

Publishing

FromHow
RESTPOST /v1/events/custom/order_shipped with { "payload": { … } } → 202 { eventId, type, version, occurredAt, webhooks }. API keys and OAuth tokens need PUBLISH_EVENTS (which implies READ_EVENTS) and act as their owner.
Functionsawait salesos.events.publish('custom.order_shipped', { orderId })
FlowsThe PUBLISH_EVENT element: eventApiName, payload mappings, optional storeAs for the event id.
WorkflowsA Publish event step: { "eventApiName": "custom.order_shipped", "payloadMapping": { "orderId": "id", "source": { "value": "crm" } } } maps record fields (or literals) into the payload.

Every publish is checked before anything is delivered:

  • the type exists in your organization and is on (404 EVENT_TYPE_NOT_FOUND, 409 EVENT_TYPE_INACTIVE);
  • the payload matches the current schema, with no unknown fields (400 CUSTOM_EVENT_INVALID, with the errors), and is at most 64 KB (413 CUSTOM_EVENT_TOO_LARGE);
  • the organization is within 600 events per minute across every source (429 CUSTOM_EVENT_RATE_LIMITED);
  • with publishAccess: "ADMINS" (the default) only org owners and admins may publish over REST (403 EVENT_PUBLISH_FORBIDDEN); "MEMBERS" lets any member publish. Your own functions, flows and workflows may always publish.

Subscribing

The event is delivered in the standard envelope, with the validated payload as data:

{
  "id": "evt_3f9c2a7d1b8e4c60a5d2f1e0b9c8a7d6",
  "type": "custom.order_shipped",
  "version": 2,
  "occurredAt": "2026-10-06T09:30:00.000Z",
  "organizationId": "org_4k2j9x",
  "data": { "orderId": "SO-1042", "carrier": "UPS" }
}
SubscriberHow
WebhooksPick the type in the event list (or "events": ["custom.order_shipped"]). Signing, retries, the delivery log and replay are the same as for catalog events.
FunctionsAdd the type to the function's eventTypes; input is the envelope.
FlowsAdd it to an autolaunched flow's eventTypes; the payload is {!$Event.orderId}.
salesos listensalesos listen --events custom.order_shipped (or custom.* for all of them).
GraphQLsubscription { customEvents(types: ["custom.order_shipped"]) { id type version occurredAt payload } } — keys and tokens need READ_EVENTS or PUBLISH_EVENTS.

A type cannot be deleted while a webhook, function or flow subscribes to it (409 EVENT_TYPE_IN_USE); turn it off instead. Subscribing to a type your organization doesn't have is 400 UNKNOWN_EVENT_TYPE. A function never receives events caused by its own writes or publishes, and a flow never re-runs for an event it published itself.

GET /v1/events/org-catalog returns the public catalog plus your custom types (any member; keys need READ_EVENTS).

Metadata and apps

Types move between orgs as CUSTOM_EVENT_TYPE components (events/<apiName>.event.json); a changed schema becomes the target's next version. App manifests can declare them under customEventTypes, and the app's webhooks may subscribe to the types it declares.

On this page