worker.updated Event

Emitted when profile-level fields on a worker change. Specifically, fired on changes to:

  • email
  • firstName, lastName (and the derived fullName)
  • phone
  • dateOfBirth
  • address (any line)
  • organizationUnit
  • organizationLocation

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.

PropertyValue
Typeworker.updated
Data version1
Entityentity.type = "worker", entity.id = data.workerId
Fan-outOne event per client the worker has at least one EXECUTED contract under
Payload shapeIdentical 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

Identical 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; a changedFields listing 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 clientId in the routing fields, the client-scoped workerId, and the same workerProfileId.
  • 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.updated events for the same worker may arrive out of occurredAt order. Use occurredAt to order on your side; never rely on arrival order.
  • A worker.updated may arrive before the corresponding worker.onboarded for 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."