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).

PropertyValue
Typeworker.onboarded
Data version1
Entityentity.type = "worker", entity.id = data.workerId
Fan-outOne delivery per matching subscription
Mutual exclusionSuppresses 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

The body mirrors the public API's WorkerV2 response shape exactly, plus two routing fields (clientId, workerProfileId).

FieldTypeDescription
clientIdstringPublic client ID this Worker row belongs to.
workerIdstringPublic worker ID (lglsowpr_…). Same value as entity.id. Per-client.
workerProfileIdstringPublic worker-profile ID (lglsowpf_…). Global — same person across multiple clients shares this.
emailstringEmail at the worker profile.
firstNamestring | nullFirst name.
lastNamestring | nullLast name.
fullNamestring | nullCombined full name.
phonestring | nullPhone in E.164 format if present.
dateOfBirthstring | nullISO-8601 date (no time component) or null if unknown.
address.address1string | nullFirst address line.
address.address2string | nullSecond address line.
address.citystring | nullCity.
address.isoCountryCodestring | nullISO-3166 alpha-2 country code.
address.postalCodestring | nullPostal/ZIP code.
address.zoneCodestring | nullState / province / region.
organizationUnit.idstringPublic org-unit ID, when the worker is assigned to a unit.
organizationUnit.namestringDisplay name of the unit. Snapshot at emit time.
organizationUnitobject | nullnull when the worker has no organization-unit assignment.
organizationLocation.idstringPublic org-location ID.
organizationLocation.namestringDisplay name of the location. Snapshot at emit time.
organizationLocationobject | nullnull when the worker has no organization-location assignment.
contractsarrayAll contracts for this Worker — same shape as WorkerV2.contracts from the public API. No status filtering.
links.selfstringPath to fetch the current state via the public v2 API.

🛈 Many-to-many org units excluded for now. Only the worker's single primary organizationUnitId is included. Listo also tracks a separate many-to-many "org units the worker can act on" relation; that set is not shipped in worker.* payloads pending a separate product decision. If you need it today, fetch via the org-structure endpoint.

contracts[] element

Each entry mirrors the public WorkerContract schema:

FieldTypeDescription
idstringPublic contract ID (lglsoctc_…).
clientIdstringSame as the parent worker's clientId.
namestringContract display name.
statusstringContract status (e.g. DRAFT, PENDING_SIGNATURE, EXECUTED, TERMINATED).
billingFrequencystringE.g. MONTHLY, BIWEEKLY.
isoCountryCodestringCountry of work.
jobTitlestringJob title at the moment of emit.
startDateISO-8601 dateContract start.
contractEndDatestring | nullISO-8601 date or null for open-ended.
createdAtISO-8601 UTCWhen the contract record was created in Listo.
engagementTypestringE.g. EOR, CONTRACTOR.
managerIdstring | nullPublic 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.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.onboarded may arrive after a worker.updated for the same worker if the producer's emit order and the network path conspire — handle gracefully.
  • The parent client.created event 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.