worker.onboarded Event
Emitted exactly once per (worker, client) pair, at the moment the
worker's first contract for that client transitions to EXECUTED (i.e.
the worker has just been onboarded under that client).
| Property | Value |
|---|---|
| Type | worker.onboarded |
| Data version | 1 |
| Entity | entity.type = "worker", entity.id = data.workerId |
| Fan-out | One delivery per matching subscription |
| Mutual exclusion | Suppresses worker.contract.updated for the same EXECUTED transition (you receive this) |
A "Worker" record in Listo is per-client (a single person under two
different clients is two Worker rows sharing one WorkerProfile), so
"once per (worker, client)" is a property of the underlying row identity
— there is no extra dedupe state on Listo's side.
Sample delivery
POST /your/webhook/path HTTP/1.1
Host: hooks.your-domain.com
Content-Type: application/json
User-Agent: ListoGlobal-Gateway/1.0
webhook-id: lglsoevt_uZL2qPRsTH9NbXeC1
webhook-timestamp: 1746183744
webhook-signature: v1,4cD8vNqLaYrU1zKj9wXfZbHpQE3oTBnVxMKgcLs5hAY=
{
"id": "lglsoevt_uZL2qPRsTH9NbXeC1",
"type": "worker.onboarded",
"specVersion": 1,
"dataVersion": 1,
"occurredAt": "2026-05-02T11:42:24.118Z",
"entity": { "type": "worker", "id": "lglsowpr_uZIIQiu8Q5zhUXgbR" },
"data": {
"clientId": "lglsocli_uZIIHfKqYBwyaRGGs",
"workerId": "lglsowpr_uZIIQiu8Q5zhUXgbR",
"workerProfileId": "lglsowpf_uYZRk9GHTcCqMpL4n",
"email": "[email protected]",
"firstName": "Alex",
"lastName": "Kim",
"fullName": "Alex Kim",
"phone": "+12025550101",
"dateOfBirth": "1992-08-14",
"address": {
"address1": "742 Evergreen Terrace",
"address2": null,
"city": "Springfield",
"isoCountryCode": "US",
"postalCode": "97402",
"zoneCode": "OR"
},
"organizationUnit": { "id": "lglsoorgu_uVwPa92qXz", "name": "Engineering" },
"organizationLocation": { "id": "lglsoorgl_uTpQa83jYx", "name": "Springfield HQ" },
"contracts": [
{
"id": "lglsoctc_uXYZ1aBcD2EfG3hJ4",
"clientId": "lglsocli_uZIIHfKqYBwyaRGGs",
"name": "Alex Kim — Senior Engineer",
"status": "EXECUTED",
"billingFrequency": "MONTHLY",
"isoCountryCode": "US",
"jobTitle": "Senior Engineer",
"startDate": "2026-05-02",
"contractEndDate": null,
"createdAt": "2026-04-28T17:09:11.000Z",
"engagementType": "EOR",
"managerId": "lglsousr_uXYZxLtq9ABvCdEf2"
}
],
"links": {
"self": "/v2/instance-integrations/inst_abc123xyz/clients/lglsocli_uZIIHfKqYBwyaRGGs/workers/lglsowpr_uZIIQiu8Q5zhUXgbR"
}
}
}data schema
data schemaThe body mirrors the public API's WorkerV2 response shape exactly, plus
two routing fields (clientId, workerProfileId).
| Field | Type | Description |
|---|---|---|
clientId | string | Public client ID this Worker row belongs to. |
workerId | string | Public worker ID (lglsowpr_…). Same value as entity.id. Per-client. |
workerProfileId | string | Public worker-profile ID (lglsowpf_…). Global — same person across multiple clients shares this. |
email | string | Email at the worker profile. |
firstName | string | null | First name. |
lastName | string | null | Last name. |
fullName | string | null | Combined full name. |
phone | string | null | Phone in E.164 format if present. |
dateOfBirth | string | null | ISO-8601 date (no time component) or null if unknown. |
address.address1 | string | null | First address line. |
address.address2 | string | null | Second address line. |
address.city | string | null | City. |
address.isoCountryCode | string | null | ISO-3166 alpha-2 country code. |
address.postalCode | string | null | Postal/ZIP code. |
address.zoneCode | string | null | State / province / region. |
organizationUnit.id | string | Public org-unit ID, when the worker is assigned to a unit. |
organizationUnit.name | string | Display name of the unit. Snapshot at emit time. |
organizationUnit | object | null | null when the worker has no organization-unit assignment. |
organizationLocation.id | string | Public org-location ID. |
organizationLocation.name | string | Display name of the location. Snapshot at emit time. |
organizationLocation | object | null | null when the worker has no organization-location assignment. |
contracts | array | All contracts for this Worker — same shape as WorkerV2.contracts from the public API. No status filtering. |
links.self | string | Path to fetch the current state via the public v2 API. |
🛈 Many-to-many org units excluded for now. Only the worker's single primary
organizationUnitIdis included. Listo also tracks a separate many-to-many "org units the worker can act on" relation; that set is not shipped inworker.*payloads pending a separate product decision. If you need it today, fetch via the org-structure endpoint.
contracts[] element
contracts[] elementEach entry mirrors the public WorkerContract schema:
| Field | Type | Description |
|---|---|---|
id | string | Public contract ID (lglsoctc_…). |
clientId | string | Same as the parent worker's clientId. |
name | string | Contract display name. |
status | string | Contract status (e.g. DRAFT, PENDING_SIGNATURE, EXECUTED, TERMINATED). |
billingFrequency | string | E.g. MONTHLY, BIWEEKLY. |
isoCountryCode | string | Country of work. |
jobTitle | string | Job title at the moment of emit. |
startDate | ISO-8601 date | Contract start. |
contractEndDate | string | null | ISO-8601 date or null for open-ended. |
createdAt | ISO-8601 UTC | When the contract record was created in Listo. |
engagementType | string | E.g. EOR, CONTRACTOR. |
managerId | string | null | Public user ID of the manager, or null. |
Snapshot semantics
All embedded fields (worker profile and every contract in contracts[])
are a snapshot at emit time. If the worker is edited between the emit
and your handler running it (typical for retries), the snapshot may be
stale.
When you need current state, refetch via the embedded link:
GET https://gateway.listoglobal.com{links.self}
x-api-key: <your-key>
The shape returned matches data exactly (less the routing fields
clientId and workerProfileId which are already in the URL).
Mutual exclusion with worker.contract.updated
worker.contract.updatedworker.onboarded and worker.contract.updated
are mutually exclusive at the source for the same status transition: when
a contract moves to EXECUTED for the first time on this worker-client
pair, you receive worker.onboarded and do not receive a
corresponding worker.contract.updated. Subsequent contract changes
(status moves on later contracts, job-title edits, etc.) emit
worker.contract.updated as normal.
This means: do not double-handle a single EXECUTED transition by
listening for both events.
Ordering and duplication
- Duplicates are normal. Dedupe on
webhook-id. - Order is not guaranteed. A
worker.onboardedmay arrive after aworker.updatedfor the same worker if the producer's emit order and the network path conspire — handle gracefully. - The parent
client.createdevent for the worker's client may arrive after this event. Don't assume the client already exists in your store.
Common patterns
"Push new hires into our HRIS"
worker.onboarded is the canonical "first day" signal — strictly stronger
than "saw a worker in worker.updated" because it fires exactly once per
(worker, client) and only on contract execution. Mirror the snapshot,
keyed by (clientId, workerId), and use workerProfileId to deduplicate
the same physical person across multiple client engagements.
"Provision worker tooling on hire"
The embedded snapshot carries everything you need to provision (email,
manager, job title, country) without an extra API round-trip. A retried
delivery may carry stale fields, so re-fetch via links.self at the start
of any provisioning workflow that needs strict freshness.
Updated 1 day ago
