Skip to main content

Webhooks request

When a webhook event fires, Logto sends a POST request to every endpoint subscribed to it. The full event catalog lives in Webhooks events; this page documents the shape of the request Logto delivers.

Request headers

KeyCustomizableNotes
user-agentLogto (https://logto.io/) by default.
content-typeapplication/json by default.
logto-signature-sha-256Signature of the request body. See securing your webhooks.

Customizable headers can be overridden via the secure webhook configuration.

Request body overview

The body is a JSON object. Its exact shape depends on which family the event belongs to:

FamilyEventsWhen it fires
User flowPostRegister, PostSignIn, PostResetPasswordA user completes a sign-up, sign-in, or password-reset flow handled by the Experience API.
Data mutationUser.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*The underlying data model is mutated by a Management API call or a user flow on the Experience API.
ExceptionIdentifier.LockoutA security incident, for example an account locked after consecutive failed verification attempts.

Every family shares a small set of common fields. Each family then layers on its own request-context fields plus an event-specific payload.

Common fields

Present in every delivery regardless of family:

FieldTypeOptionalNotes
hookIdstringThe webhook configuration identifier in Logto.
eventstringThe event that triggered this delivery.
createdAtstringThe payload creation time in ISO 8601 format.
userAgentstringThe user-agent of the triggering request.

Each family also includes the IP address of the triggering request, under the field name userIp for user-flow events and ip for data-mutation and exception events. The semantics are identical; the historical name difference is preserved for backward compatibility.

User flow event payloads

Events: PostRegister, PostSignIn, PostResetPassword.

Fired when a user completes a sign-up, sign-in, or password-reset flow handled by the Experience API. In addition to the common fields, the body carries:

FieldTypeOptionalNotes
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'The user-flow event type. Maps to PostSignIn / PostRegister / PostResetPassword respectively. The field name retains historical "interaction" naming.
sessionIdstringThe Session ID (not Interaction ID) for this event, if applicable.
userIpstringThe IP address of the triggering request.
userIdstringThe User ID associated with this event, if applicable.
userUserEntityThe user entity associated with this event, if applicable.
applicationIdstringThe Application ID associated with this event, if applicable.
applicationApplicationEntityThe application entity associated with this event, if applicable.

Entity shapes

type UserEntity = {
id: string;
username?: string;
primaryEmail?: string;
primaryPhone?: string;
name?: string;
avatar?: string;
customData?: object;
identities?: object;
lastSignInAt?: string;
createdAt?: string;
applicationId?: string;
isSuspended?: boolean;
};
enum ApplicationType {
Native = 'Native',
SPA = 'SPA',
Traditional = 'Traditional',
MachineToMachine = 'MachineToMachine',
Protected = 'Protected',
SAML = 'SAML',
}

type ApplicationEntity = {
id: string;
type: ApplicationType;
name: string;
description?: string;
};

See Users and Applications for the full field reference.

Data mutation event payloads

Events: every event under User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*. See Webhooks events → Data mutation hook events for the complete catalog.

The body always carries:

  • The common fields.
  • An ip field, the IP address of the triggering request (optional, present when known).
  • An API context describing how the change was triggered. The context is one of two variants depending on the trigger source:
  • An event-specific payload: the affected entity in data and (for some events) additional top-level fields. See event-specific data payloads.

Experience API context fields

Present when the change was triggered by a user-facing flow on the Experience API, for example User.Created during sign-up or User.Data.Updated during profile updates.

FieldTypeOptionalNotes
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'The user-flow event type that produced the change. Field name retains historical "interaction" naming.
sessionIdstringThe Session ID (not Interaction ID) for this event, if applicable.
applicationIdstringThe Application ID, if applicable.
applicationApplicationEntityThe application entity, if applicable.

Management API context fields

Present when the change was triggered by a Management API call.

FieldTypeOptionalNotes
pathstringThe path of the API call that triggered this hook.
methodstringThe HTTP method of the API call.
statusnumberThe response status code of the API call.
paramsobjectThe koa path params of the API call.
matchedRoutestringThe koa matched route. Logto uses this field to match enabled webhook event filters.

Event-specific data payloads

Every data-mutation event includes a top-level data field carrying the affected entity, or null when the change can't be summarized as a single entity (delete and membership events). Some events also include event-specific top-level fields beyond data; Organization.Membership.Updated is one such case, documented below.

User events

EventFieldTypeOptionalNotes
User.CreateddataUserEntityThe created user entity.
User.Data.UpdateddataUserEntityThe updated user entity.
User.Deleteddatanull/

Role events

type Role = {
id: string;
name: string;
description: string;
type: 'User' | 'MachineToMachine';
isDefault: boolean;
};
type Scope = {
id: string;
name: string;
description: string;
resourceId: string;
createdAt: number;
};
EventFieldTypeOptionalNotes
Role.CreateddataRoleThe created role entity.
Role.Data.UpdateddataRoleThe updated role entity.
Role.Deleteddatanull/
Role.Scopes.UpdateddataScope[]The updated scopes assigned to the role.
Role.Scopes.UpdatedroleIdstringThe role ID that scopes are assigned to. (Only available when the event was triggered by creating a role with pre-assigned scopes.)

Permission (Scope) events

EventFieldTypeOptionalNotes
Scope.CreateddataScopeThe created scope entity.
Scope.Data.UpdateddataScopeThe updated scope entity.
Scope.Deleteddatanull/

Organization events

type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
EventFieldTypeOptionalNotes
Organization.CreateddataOrganizationThe created organization entity.
Organization.Data.UpdateddataOrganizationThe updated organization entity.
Organization.Deleteddatanull/
Organization.Membership.Updateddatanull/The change is described by optional top-level delta arrays. See Organization.Membership.Updated payload below.
Organization.Membership.Updated payload

In addition to the common fields and the API-context fields that apply for the trigger source (Management API context for Management API routes, Experience API context for just-in-time provisioning), the Organization.Membership.Updated event carries an organizationId plus optional delta arrays at the top level of the payload (next to event, createdAt, etc., not inside data, which is always null for this event).

FieldTypeOptionalNotes
organizationIdstringThe organization whose membership changed.
addedUserIdsstring[]User IDs newly added by this trigger. Omitted when no users were added, or when the trigger does not affect user membership.
removedUserIdsstring[]User IDs removed by this trigger. Omitted when no users were removed.
addedApplicationIdsstring[]Application IDs newly added. Omitted when no applications were added, or when the trigger does not affect application membership.
removedApplicationIdsstring[]Application IDs removed. Omitted when no applications were removed.

The four delta arrays are optional and additive: they don't change the existing payload shape for consumers that don't expect them, and the legacy data: null field is still emitted unchanged.

Triggers and which delta fields they may emit
TriggerPossible delta fields
POST /organizations/:id/usersaddedUserIds
PUT /organizations/:id/usersaddedUserIds, removedUserIds
DELETE /organizations/:id/users/:userIdremovedUserIds
POST /organizations/:id/applicationsaddedApplicationIds
PUT /organizations/:id/applicationsaddedApplicationIds, removedApplicationIds
DELETE /organizations/:id/applications/:applicationIdremovedApplicationIds
PUT /organization-invitations/:id/status (Accepted)addedUserIds
Just-in-time provisioning when adding the user to a new organizationaddedUserIds
Empty deltas are omitted (absent ≠ empty change)

Empty delta arrays are omitted entirely from the payload. For example, a PUT /organizations/:id/users that replaces the membership set with the existing set produces no real change, and the payload reduces to just { organizationId } with all four delta fields absent. The same applies to a re-add of an existing member and a re-accept of an invitation by a user who is already a member.

Consumers must treat a missing field as "no change on that side," not as "an empty change."

Per-array cap (silent truncation)

Each delta array is capped at 5000 entries. When a single Management API call adds or removes more than 5000 users (or applications) in one operation, the corresponding delta array is silently truncated to its first 5000 entries. There is no in-payload marker that a cap fired.

If your application performs administrative bulk operations that can plausibly affect more than 5000 members in one call, treat an array of exactly 5000 entries as a signal to reconcile authoritative membership via the Management API:

  • GET /organizations/:id/users: full user membership.
  • GET /organizations/:id/applications: full application membership.

This follows the same pattern as GitHub's push event, which caps commits at 20 entries and points consumers at the compare API for the full list.

Skipping no-op events

To skip no-op deliveries (events with no delta fields) on the consumer side, filter on delta-array presence:

if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// real membership change, handle it
}

?.length is falsy for both undefined and [], so the same predicate is robust whether the field is absent or (in some hypothetical future) emitted as an empty array.

Example payloads

Add a user (POST /organizations/:id/users):

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_001"]
}

Replace the user membership set (PUT /organizations/:id/users):

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_002"],
"removedUserIds": ["u_001"]
}

Remove a user (DELETE /organizations/:id/users/:userId):

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_001"]
}

Add an application (POST /organizations/:id/applications):

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedApplicationIds": ["app_xyz"]
}

Re-add an existing member, no-op PUT, or re-accept of an already-member invitation (no real change):

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc"
}

Bulk operation that hits the 5000 cap (silently truncated):

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_0001", "u_0002", "/* … exactly 5000 entries total */"]
}

Seeing an array of exactly 5000 entries should prompt a reconciling GET /organizations/:id/users (or /applications).

Organization role events

type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
EventFieldTypeOptionalNotes
OrganizationRole.CreateddataOrganizationRoleThe created organization role entity.
OrganizationRole.Data.UpdateddataOrganizationRoleThe updated organization role entity.
OrganizationRole.Deleteddatanull/
OrganizationRole.Scopes.Updateddatanull/
OrganizationRole.Scopes.UpdatedorganizationRoleIdstringThe role ID that scopes are assigned to. (Only available when the event was triggered by creating a role with pre-assigned scopes.)

Organization permission (scope) events

EventFieldTypeOptionalNotes
OrganizationScope.CreateddataOrganizationScopeThe created organization scope entity.
OrganizationScope.Data.UpdateddataOrganizationScopeThe updated organization scope entity.
OrganizationScope.Deleteddatanull/

Exception event payloads

Events: Identifier.Lockout.

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:

enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
FieldTypeOptionalNotes
typeSignInIdentifierThe user's identifier type, e.g., email, phone or username.
valuestringThe user's identifier value that triggered the lockout.