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 derivedfullName)status— an active-status transition
(ACTIVE/INACTIVE/INVITED/DELETED), e.g. theINVITED→
ACTIVEpromotion 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.
| Property | Value |
|---|---|
| Type | user.updated |
| Data version | 1 |
| Entity | entity.type = "user", entity.id = data.userId |
| Fan-out | One delivery per matching subscription on the user's parent client |
| Payload shape | Superset 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
data schema| Field | Type | Description |
|---|---|---|
userId | string | Public user ID (lglsousr_…). Same value as entity.id. |
clientId | string | Public client ID this user belongs to (lglsocli_…). |
email | string | Email address from the user's credential. |
firstName | string | null | First name. Snapshot at emit time. |
lastName | string | null | Last name. |
fullName | string | null | Combined full name. |
status | string | Account status: ACTIVE, INACTIVE, INVITED, or DELETED. |
manager | object | null | The user's assigned manager, or null. See below. |
role | object | null | The user's access role, or null. See below. |
createdAt | ISO-8601 UTC | When the user record was originally created in Listo. Immutable. |
links.self | string | Path to fetch the current state of this user. Already substituted with your instanceIntegrationId before signing. |
manager object
manager object| Field | Type | Description |
|---|---|---|
id | string | Public user ID of the manager (lglsousr_…). |
firstName | string | null | Manager's first name. |
lastName | string | null | Manager's last name. |
fullName | string | null | Manager's full name. |
email | string | Manager's email. |
role object
role object| Field | Type | Description |
|---|---|---|
id | string | Internal access-role id. Not a public ID — access roles have no public ID. |
name | string | Role 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,roleisnull.
Relationship to user.created
user.createduser.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.updatedevents for the same user
may arrive out ofoccurredAtorder. Order byoccurredAton your side. - A
user.updatedmay arrive before the correspondinguser.created
for the same user, or before the parent
client.created.
Upsert speculatively onuserIdrather 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.
Updated about 1 month ago
