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
| Type | Description | Entity | Data version |
|---|---|---|---|
client.created | A new client has been created on the instance. | client | 1 |
user.created | A new dashboard user has been created for a client. | user | 1 |
worker.onboarded | A worker's first contract for a client has been executed. Fires exactly once per (worker, client) pair. | worker | 1 |
worker.updated | A worker's profile fields (email, name, address, phone, date of birth, organization unit/location) have changed. | worker | 1 |
worker.contract.updated | A worker's contract status, jobTitle, or managerId has changed. Suppressed for the EXECUTED transition that triggers worker.onboarded. | workerContract | 1 |
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:
| Prefix | Meaning |
|---|---|
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:
| Event | Fan-out |
|---|---|
client.created | One per matching subscription. |
user.created | One per matching subscription. |
worker.onboarded | One per matching subscription. Fires once per (worker, client) pair, ever. |
worker.updated | One 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.updated | One 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.onboarded ↔ worker.contract.updated
worker.onboarded ↔ worker.contract.updatedThese 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-levelid). - Order is not guaranteed — across types, across entities, or even within a single entity. Use
occurredAtto order on your side if order matters. Aworker.updatedmay arrive before the correspondingworker.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 oftype, structural changes to the outer object). Currently1. - Per-type data version (
dataVersion): bumps independently when a single event type'sdatashape breaks. Each type owns its own lineage. - Additive changes (new optional
datafields, 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 for | Notes |
|---|---|---|
| New tenant onboarding | client.created | Fires once per client. Pair with worker.onboarded if you wait for the first hire. |
| New dashboard user (e.g. SSO provisioning) | user.created | No user.updated event today — poll the users endpoint for later edits. |
| New hire ("first day" signal) | worker.onboarded | Strictly stronger than seeing the worker in worker.updated: fires exactly once per (worker, client). |
| Worker profile changes (address, phone, name, org unit) | worker.updated | Fans out per client the worker is engaged at. |
| Contract lifecycle (signed, terminated) | worker.contract.updated | Excluding the first-EXECUTED transition, which surfaces as worker.onboarded. |
| Manager re-assignment | worker.contract.updated | managerId lives on the contract, not the worker, so manager moves flow through this event. |
| Job-title change | worker.contract.updated | Likewise — 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 types | Subscribe to all | Tolerate unknown types; return 200. |
Per-event references
Updated 1 day ago
