user.updated Event

Emitted when an existing dashboard User changes. A "user" here is a
person who can sign in to the Listo dashboard on behalf of the client —
distinct from a worker (an employee or contractor receiving payroll).

Fired on changes to any of:

  • email, firstName, lastName (and the derived fullName)
  • status — an active-status transition
    (ACTIVE / INACTIVE / INVITED / DELETED), e.g. the INVITED
    ACTIVE promotion on first login, or an enable / disable.
  • manager — the user's assigned manager was set, changed, or cleared.
  • role — the user's access role was (re)assigned (including when editing
    a permission produces a new custom role and the user is re-pointed at it).

The payload is a full snapshot of the user's current state, not a diff.

PropertyValue
Typeuser.updated
Data version1
Entityentity.type = "user", entity.id = data.userId
Fan-outOne delivery per matching subscription on the user's parent client
Payload shapeSuperset of user.created — adds status, manager, and role

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_uZP5tQVwDL9NcZhF4
webhook-timestamp: 1746444600
webhook-signature: v1,9rXmT3pA6bNqWJk8zFGwMCdYIEtpDMfHKyRYN4zUcZa=

{
  "id": "lglsoevt_uZP5tQVwDL9NcZhF4",
  "type": "user.updated",
  "specVersion": 1,
  "dataVersion": 1,
  "occurredAt": "2026-05-05T09:21:14.512Z",
  "entity": { "type": "user", "id": "lglsousr_uXYZxLtq9ABvCdEf2" },
  "data": {
    "userId": "lglsousr_uXYZxLtq9ABvCdEf2",
    "clientId": "lglsocli_uZIIHfKqYBwyaRGGs",
    "email": "[email protected]",
    "firstName": "Kalin",
    "lastName": "Sasaki",
    "fullName": "Kalin Sasaki",
    "status": "ACTIVE",
    "manager": {
      "id": "lglsousr_uABc1dEf2gHi3jKl4",
      "firstName": "Dana",
      "lastName": "Cruz",
      "fullName": "Dana Cruz",
      "email": "[email protected]"
    },
    "role": {
      "id": "5c1f0c2e-7c0e-4f1a-9b3d-2a1b6e4d8f90",
      "name": "Admin"
    },
    "createdAt": "2026-04-01T09:00:00.000Z",
    "links": {
      "self": "/v1/instance-integrations/inst_abc123xyz/clients/lglsocli_uZIIHfKqYBwyaRGGs/users/lglsousr_uXYZxLtq9ABvCdEf2"
    }
  }
}

data schema

FieldTypeDescription
userIdstringPublic user ID (lglsousr_…). Same value as entity.id.
clientIdstringPublic client ID this user belongs to (lglsocli_…).
emailstringEmail address from the user's credential.
firstNamestring | nullFirst name. Snapshot at emit time.
lastNamestring | nullLast name.
fullNamestring | nullCombined full name.
statusstringAccount status: ACTIVE, INACTIVE, INVITED, or DELETED.
managerobject | nullThe user's assigned manager, or null. See below.
roleobject | nullThe user's access role, or null. See below.
createdAtISO-8601 UTCWhen the user record was originally created in Listo. Immutable.
links.selfstringPath to fetch the current state of this user. Already substituted with your instanceIntegrationId before signing.

manager object

FieldTypeDescription
idstringPublic user ID of the manager (lglsousr_…).
firstNamestring | nullManager's first name.
lastNamestring | nullManager's last name.
fullNamestring | nullManager's full name.
emailstringManager's email.

role object

FieldTypeDescription
idstringInternal access-role id. Not a public ID — access roles have no public ID.
namestringRole name (e.g. Owner, Admin, or a custom role name). Match on name for portability.

🛈 The user's role is singular. Listo treats a user's role as
accessRoles[0]. If a user has no role assigned, role is null.


Relationship to user.created

user.updated carries the same shape as
user.created
— both include status, manager, and role. The two events differ only
in semantics: user.created fires once when the user is first created
(after the default manager and initial role are assigned);
user.updated fires on every subsequent change. You can handle both with
a single upsert keyed by userId.


Snapshot semantics

The body is a snapshot at emit time, not a diff — it does not tell you
which field changed. Diff against your stored copy if you care. If multiple
edits happen close together you will receive multiple events, each carrying
the full snapshot at its own emit moment. Combined with at-least-once
delivery and best-effort ordering, a late retry of an older event can
arrive after a newer one — refetch via links.self when you need strict
"latest state":

GET https://gateway.listoglobal.com{links.self}
x-api-key: <your-key>

createdAt is the immutable creation timestamp — it does not drift on
re-fetch.


Ordering and duplication

  • Duplicates are normal. Dedupe on webhook-id.
  • Order is not guaranteed. Two user.updated events for the same user
    may arrive out of occurredAt order. Order by occurredAt on your side.
  • A user.updated may arrive before the corresponding user.created
    for the same user, or before the parent
    client.created.
    Upsert speculatively on userId rather than insisting the record already
    exists.

Common patterns

"Keep a dashboard-user mirror in sync"

1. Verify signature, dedupe on webhook-id.
2. UPSERT INTO users (user_id, client_id, ...) VALUES (...)
   ON CONFLICT (user_id) DO UPDATE
   SET email = EXCLUDED.email,
       status = EXCLUDED.status,
       manager_id = EXCLUDED.manager_id,
       role_name = EXCLUDED.role_name,
       last_event_at = EXCLUDED.last_event_at
   WHERE users.last_event_at < EXCLUDED.last_event_at;
3. Respond 200.

The last_event_at guard prevents a stale retry from overwriting newer state.

"React to deactivation"

Filter on data.status. A transition to INACTIVE or DELETED is your
cue to revoke downstream access (SSO, RBAC) for that user. A transition to
ACTIVE (e.g. from INVITED on first login) is the cue to provision.

"Track role / permission changes"

Hook data.role. Editing a permission within a role can mint a new custom
role and re-point the user at it — that re-assignment surfaces here as a
user.updated with the new role.name. Match on name, not id.


Did this page help you?