worker.updated Event
Emitted when profile-level fields on a worker change. Specifically, fired on changes to:
emailfirstName,lastName(and the derivedfullName)phonedateOfBirthaddress(any line)organizationUnitorganizationLocation
Fan-out is per-client: a single change at the underlying
WorkerProfile (e.g. updating a phone number) emits one event per
Worker row that has at least one EXECUTED contract — i.e. one event
per client where the person is actively engaged. The shared
workerProfileId lets you correlate the events as the same underlying
person.
Workers without any executed contract (e.g. drafts, pending signature) are not eligible — you do not receive updates for them.
| Property | Value |
|---|---|
| Type | worker.updated |
| Data version | 1 |
| Entity | entity.type = "worker", entity.id = data.workerId |
| Fan-out | One event per client the worker has at least one EXECUTED contract under |
| Payload shape | Identical to worker.onboarded — pure full snapshot |
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_uZM3rPSsBJ7NbXfD2
webhook-timestamp: 1746271800
webhook-signature: v1,Tk2pVxQ3rZ4cNwLyB8EHmRYbFGJaCQ5dKHxsXM7uTvQ=
{
"id": "lglsoevt_uZM3rPSsBJ7NbXfD2",
"type": "worker.updated",
"specVersion": 1,
"dataVersion": 1,
"occurredAt": "2026-05-03T12:10:00.231Z",
"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": "+12025550199",
"dateOfBirth": "1992-08-14",
"address": {
"address1": "742 Evergreen Terrace",
"address2": "Apt 3",
"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 schemaIdentical to worker.onboarded
— the two events share the WorkerSnapshotData shape. Refer to that page
for full field definitions; only the semantics differ.
🛈 Pure snapshot — no
changedFields. The body does not tell you which field changed. Diff against your prior stored state if you care. Listo deliberately keeps the payload minimal and authoritative; achangedFieldslisting would be one more thing to keep in sync as the snapshot grows.
Fan-out semantics in detail
A single edit at WorkerProfile fans out to one event per matching
Worker row. Concretely:
- Person at one client: one event.
- Person at three clients (three executed contracts): three events,
one per client. Each carries that client's
clientIdin the routing fields, the client-scopedworkerId, and the sameworkerProfileId. - Person at three clients but only one has an executed contract: one event (only the executed-contract Worker row is eligible).
The workerProfileId field is the join key for treating the multiple
deliveries as the same person. Do not compare workerIds across
deliveries to identify a person — workerId is per-client.
Snapshot semantics
All embedded fields are a snapshot at emit time. If multiple updates
happen close together you will receive multiple events; each carries the
full snapshot at its own emit moment. Combined with at-least-once delivery
and best-effort ordering, this means a late retry of an older event can
arrive after a newer one — refetch via links.self if you need strict
"latest state."
Ordering and duplication
- Duplicates are normal. Dedupe on
webhook-id. - Order is not guaranteed. Two
worker.updatedevents for the same worker may arrive out ofoccurredAtorder. UseoccurredAtto order on your side; never rely on arrival order. - A
worker.updatedmay arrive before the correspondingworker.onboardedfor the same worker — this happens when the order in which Listo's queue drains diverges from the order things changed. Tolerate it: upsert on(clientId, workerId)rather than insisting on a specific arrival sequence.
Common patterns
"Keep a worker mirror in sync"
1. Verify signature, dedupe on webhook-id.
2. UPSERT INTO workers (client_id, worker_id) VALUES (...) ON CONFLICT
DO UPDATE SET <fields>, last_event_at = data.occurredAt
WHERE workers.last_event_at < data.occurredAt;
3. Respond 200.The last_event_at guard ensures a delayed retry of an older event does
not overwrite a more recent state.
"Diff for downstream automation"
If you need to fire an internal alert when a worker's address changes,
compare incoming data.address.* against your stored copy. Listo does
not provide change deltas; the diff is yours to compute. Consider hashing
the canonical JSON of the snapshot for cheap "did anything change"
checks before computing field-by-field diffs.
"Treat one person across many clients as one record"
Index your worker mirror by workerProfileId, not workerId, when the
business question is "is this the same person at this company?" Use
workerId (or the pair (clientId, workerId)) when the question is
"this engagement specifically."
Updated 1 day ago
