You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* docs: document Grant.LimitExceeded webhook event
* docs: point revoked grant IDs at audit logs instead of grants list
* docs: drop audit log cross-reference for revoked grant IDs
| Identifier.Lockout | A user account is locked due to consecutive failed identity verification attempts. Can be triggered in the following flows:<br /><ul><li>Password verification failed</li><li>Code verification failed</li><li>One-time token verification failed</li></ul> |
131
131
| Message.RateLimited | An end user exceeds the per-recipient [send rate limit](/security/send-rate-limit) for verification codes. Fires only on the end-user send paths (Experience API and Account API), with a payload of `{ action, recipient }`. |
132
+
| Grant.LimitExceeded | A successful authorization pushes a user past an app's [max concurrent authenticated devices](/sessions/session-configs#max-concurrent-authenticated-devices-per-app) limit (`maxAllowedGrants`), so Logto revokes their oldest grants for that app. |
|**User flow**|`PostRegister`, `PostSignIn`, `PostResetPassword`| A user completes a sign-up, sign-in, or password-reset flow handled by the Experience API. |
29
29
|**Data mutation**|`User.*`, `Role.*`, `Scope.*`, `Organization.*`, `OrganizationRole.*`, `OrganizationScope.*`| The underlying data model is mutated by a Management API call or a user flow on the Experience API. |
30
-
|**Exception**|`Identifier.Lockout`| A security incident, for example an account locked after consecutive failed verification attempts. |
30
+
|**Exception**|`Identifier.Lockout`, `Message.RateLimited`, `Grant.LimitExceeded`| A security incident, for example an account locked after consecutive failed verification attempts. |
31
31
32
32
Every family shares a small set of [common fields](#common-fields). Each family then layers on its own request-context fields plus an event-specific payload.
Fired on security incidents, for example an account locked after consecutive failed verification attempts. These events always originate from a user-facing flow, so the body carries:
370
+
Fired on security incidents, for example an account locked after consecutive failed verification attempts, or grants evicted because an app's concurrent-device limit was exceeded.
371
371
372
-
- The [common fields](#common-fields).
373
-
- The `ip` field (same shape as data-mutation events).
374
-
- The [Experience API context fields](#experience-api-context-fields).
375
-
- The exception-specific fields below.
372
+
Every exception event carries the [common fields](#common-fields) and an `ip` field (same shape as data-mutation events). The remaining fields depend on the event.
Fired when a successful authorization pushes a user past an app's [max concurrent authenticated devices](/sessions/session-configs#max-concurrent-authenticated-devices-per-app) limit (`maxAllowedGrants`) and Logto revokes their oldest grants for that app.
403
+
404
+
This event is emitted from the OIDC authorization endpoint rather than the Experience API, so it does **not** carry `interactionEvent` or `sessionId`. Alongside the common fields and `ip`, the body carries:
| userId |`string`|| The user whose grants were revoked. |
409
+
| applicationId |`string`|| The application whose `maxAllowedGrants` limit was exceeded. |
410
+
| application |`ApplicationEntity`| ✅ | The application entity. Omitted if the application can't be resolved at delivery time. |
411
+
| maxAllowedGrants |`number`|| The limit configured on the application when the event fired. |
412
+
| preRevocationActiveGrantCount |`number`|| The number of active grants the user held for this application before the revocation, including the one just issued. |
413
+
| revokedGrantIds |`string[]`|| The IDs of the grants that were actually revoked, oldest first. |
414
+
415
+
Example payload:
416
+
417
+
```json
418
+
{
419
+
"hookId": "hook_abc",
420
+
"event": "Grant.LimitExceeded",
421
+
"createdAt": "2024-01-01T00:00:00.000Z",
422
+
"ip": "192.168.0.1",
423
+
"userAgent": "Mozilla/5.0",
424
+
"userId": "u_001",
425
+
"applicationId": "app_xyz",
426
+
"application": {
427
+
"id": "app_xyz",
428
+
"type": "SPA",
429
+
"name": "My app",
430
+
"description": "My app description"
431
+
},
432
+
"maxAllowedGrants": 2,
433
+
"preRevocationActiveGrantCount": 3,
434
+
"revokedGrantIds": ["grant_001"]
435
+
}
436
+
```
437
+
438
+
Delivery notes:
439
+
440
+
- The revoked grant records are destroyed as part of the revocation, so the IDs in `revokedGrantIds` no longer resolve through the grant listing endpoints or the Console — those return **active** grants only. Treat the payload as the record, and persist the IDs on your side if you need them later.
441
+
- The event fires only when at least one grant was **actually revoked**. An authorization that stays within the limit produces no event.
442
+
- It fires on every authorization that exceeds the limit, so a user repeatedly signing in from more devices than allowed produces one event per eviction.
443
+
- Dispatch is fire-and-forget: a slow or failing endpoint never blocks or fails the user's authorization. Failed deliveries are recorded in the audit logs like any other webhook.
Copy file name to clipboardExpand all lines: docs/integrate-logto/application-data-structure.mdx
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -203,6 +203,8 @@ The OpenID Connect backchannel logout endpoint. See [Federated sign-out: Back-ch
203
203
204
204
This setting is useful when you want to cap concurrent authenticated devices per app.
205
205
206
+
Each eviction triggers the `Grant.LimitExceeded`[webhook event](/developers/webhooks/webhooks-events#exception-hook-events), so you can notify the user or track how often the limit is hit.
Copy file name to clipboardExpand all lines: docs/sessions/session-configs.mdx
+17Lines changed: 17 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -96,6 +96,20 @@ This setting is not supported for machine-to-machine apps, protected apps, and S
96
96
97
97
:::
98
98
99
+
### Monitor evictions with a webhook \{#monitor-evictions-with-a-webhook}
100
+
101
+
When Logto revokes a user's oldest grants to enforce `maxAllowedGrants`, it triggers the `Grant.LimitExceeded`[webhook event](/developers/webhooks/webhooks-events#exception-hook-events), so you can react to devices being signed out rather than discovering it from support tickets.
102
+
103
+
The payload identifies the user and app, the limit in effect, how many grants were active before the eviction, and the grant IDs that were revoked. See [Grant.LimitExceeded payload](/developers/webhooks/webhooks-request#grantlimitexceeded-payload) for the full structure.
104
+
105
+
**Common use cases:**
106
+
107
+
- Notify the user that they were signed out on another device, and from where.
108
+
- Feed device-eviction rates into your analytics to tell whether the configured limit is too low.
109
+
- Flag unusual eviction patterns for a single user as a possible credential-sharing or account-takeover signal.
110
+
111
+
Navigate to <CloudLinkto="/webhooks">Console > Webhooks</CloudLink> to subscribe, or create the hook via the Management API. See [Webhooks](/developers/webhooks) for configuration details.
112
+
99
113
## Related resources \{#related-resources}
100
114
101
115
<Urlhref="/sessions">Sessions</Url>
@@ -104,4 +118,7 @@ This setting is not supported for machine-to-machine apps, protected apps, and S
0 commit comments