Event Catalog

The pages that follow document each domain event Listo emits. This page is the parent reference: it describes the common shape every event shares, the catalog of supported types, and the cross-cutting semantics (snapshot freshness, fan-out, mutual exclusion, hypermedia links, ID prefixes) that apply across the catalog. When in doubt about a specific event, the per-event pages are authoritative. Use this page when you're picking which event to listen to, or when you want a single place that summarises behaviour shared across the whole catalog.

The common envelope

Every event Listo delivers is a single HTTP POST whose body is a DomainEvent envelope (no outer wrapper):

{
  "id": "lglsoevt_…",
  "type": "client.created",
  "specVersion": 1,
  "dataVersion": 1,
  "occurredAt": "2026-05-02T10:14:32.184Z",
  "entity": { "type": "client", "id": "lglsocli_…" },
  "data": { /* type-specific — see the per-event pages */ }
}

Top-level fields are identical across every type. data is the only field whose shape changes from one type to the next. See Overview → Anatomy of a delivery for the full envelope definition and header reference.


Catalog

TypeDescriptionEntityData version
client.createdA new client has been created on the instance.client1
user.createdA new dashboard user has been created for a client.user1
worker.onboardedA worker's first contract for a client has been executed. Fires exactly once per (worker, client) pair.worker1
worker.updatedA worker's profile fields (email, name, address, phone, date of birth, organization unit/location) have changed.worker1
worker.contract.updatedA worker's contract status, jobTitle, or managerId has changed. Suppressed for the EXECUTED transition that triggers worker.onboarded.workerContract1

The list will grow over time. Subscribe to all if you want forward-compatible coverage without coordinating a Listo-side update each time a new type lands. See Subscribing → Choosing event types.


Public ID prefixes you'll see

All identifiers in event payloads are opaque strings — never parse them. The prefixes are stable and useful for routing in your code:

PrefixMeaning
lglsoevt_Event ID. Doubles as the webhook-id header and your dedupe key.
lglsocli_Client (Client.publicClientId).
lglsousr_Dashboard user (User.publicId).
lglsowpr_Worker (per-client; Worker.publicId).
lglsowpf_Worker profile (global, shared across clients; WorkerProfile.publicWorkerProfileId).
lglsoctc_Worker contract (Contract.publicContractId).
lglsoorgu_Organization unit.
lglsoorgl_Organization location.

workerId is per-client: the same physical person engaged at two different clients has two distinct Worker rows and two distinct workerId values. Use workerProfileId when you need to deduplicate a person across clients.


Cross-cutting semantics

These properties apply uniformly across every event type. The per-event pages reiterate them in context, but the canonical statement lives here.

Snapshot semantics (every embedded payload)

Every embedded data field is a snapshot at emit time. If the source record is edited between Listo emitting the event and your handler running it — typical for retries or a delayed consumer — the snapshot may not reflect the current state.

When you need fresh data:

GET https://gateway.listoglobal.com{data.links.self}
x-api-key: <your-key>

The path in data.links.self is already substituted with your installation's instanceIntegrationId before Listo signs the body, so you can call it as-is. Other path segments (clientId, workerId, …) are inlined too; only the instanceIntegrationId placeholder is ever rewritten by Listo.

Nested resources (workers under a client, contracts under a worker that aren't directly on the event, organization structure) are not embedded in any event payload. Pull them on demand via the public REST API.

Fan-out

The number of deliveries a single source change produces depends on the event type:

EventFan-out
client.createdOne per matching subscription.
user.createdOne per matching subscription.
worker.onboardedOne per matching subscription. Fires once per (worker, client) pair, ever.
worker.updatedOne per client the worker has at least one EXECUTED contract under. A single profile edit therefore produces N events for a person engaged at N clients.
worker.contract.updatedOne per matching subscription per qualifying change.

workerProfileId is the join key for treating multiple worker.updated deliveries (or a worker.onboarded followed by later worker.updateds) as the same physical person.

Mutual exclusion: worker.onboardedworker.contract.updated

These two events are mutually exclusive at the source for a single status transition: when a contract moves to EXECUTED for the first time on a (worker, client) pair, you receive worker.onboarded and not the worker.contract.updated that would otherwise fire for that status change. Subsequent contract changes (later EXECUTED transitions on additional contracts for the same pair, job-title edits, manager changes) emit worker.contract.updated as normal.

Don't double-handle a single EXECUTED transition by listening for both events — Listo enforces the suppression at the producer side.

Idempotency and ordering apply universally

These rules are the same for every type:

  • Duplicates are normal. Listo retries up to 3 times on non-2xx, network error, or timeout. Dedupe on webhook-id (= the body's top-level id).
  • Order is not guaranteed — across types, across entities, or even within a single entity. Use occurredAt to order on your side if order matters. A worker.updated may arrive before the corresponding worker.onboarded; a child entity event may arrive before its parent. Tolerate it.

See Delivery & retries for the full contract.

Versioning

  • Envelope version (specVersion): bumps only on breaking changes to the envelope itself (rename of type, structural changes to the outer object). Currently 1.
  • Per-type data version (dataVersion): bumps independently when a single event type's data shape breaks. Each type owns its own lineage.
  • Additive changes (new optional data fields, new event types) do not bump anything. Your handler must tolerate unknown fields and unknown types gracefully.

Pin your verification logic against the (type, dataVersion) pair, not specVersion alone. See Overview → Versioning policy.


"Which event should I listen to?"

A quick decision guide, by use case:

You want to react to…Listen forNotes
New tenant onboardingclient.createdFires once per client. Pair with worker.onboarded if you wait for the first hire.
New dashboard user (e.g. SSO provisioning)user.createdNo user.updated event today — poll the users endpoint for later edits.
New hire ("first day" signal)worker.onboardedStrictly stronger than seeing the worker in worker.updated: fires exactly once per (worker, client).
Worker profile changes (address, phone, name, org unit)worker.updatedFans out per client the worker is engaged at.
Contract lifecycle (signed, terminated)worker.contract.updatedExcluding the first-EXECUTED transition, which surfaces as worker.onboarded.
Manager re-assignmentworker.contract.updatedmanagerId lives on the contract, not the worker, so manager moves flow through this event.
Job-title changeworker.contract.updatedLikewise — jobTitle is per-contract.
Org structure changes(no event today)Refetch the organization-structure endpoint when you handle worker events.
All of the above + future typesSubscribe to allTolerate unknown types; return 200.

Per-event references