Webhooks-Anfrage
Wenn ein Webhook-Ereignis ausgelöst wird, sendet Logto eine POST-Anfrage an jeden Endpunkt, der darauf abonniert ist. Den vollständigen Ereigniskatalog findest du unter Webhooks-Ereignisse; auf dieser Seite wird die Struktur der Anfrage dokumentiert, die Logto ausliefert.
Anfrage-Header
| Key | Anpassbar | Hinweise |
|---|---|---|
| user-agent | ✅ | Standardmäßig Logto (https://logto.io/). |
| content-type | ✅ | Standardmäßig application/json. |
| logto-signature-sha-256 | Signatur des Anfrage-Bodys. Siehe Webhooks absichern. |
Anpassbare Header können über die Secure Webhook-Konfiguration überschrieben werden.
Überblick über den Anfrage-Body
Der Body ist ein JSON-Objekt. Seine genaue Struktur hängt davon ab, zu welcher Familie das Ereignis gehört:
| Familie | Ereignisse | Wann es ausgelöst wird |
|---|---|---|
| User flow | PostRegister, PostSignIn, PostResetPassword | Ein Benutzer schließt einen Anmelde-, Anmelde- oder Passwort-Reset-Flow ab, der von der Experience API verarbeitet wird. |
| Data mutation | User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* | Das zugrundeliegende Datenmodell wird durch einen Management API-Aufruf oder einen User Flow auf der Experience API verändert. |
| Exception | Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded | Ein Sicherheitsvorfall, z. B. ein Konto wird nach aufeinanderfolgenden fehlgeschlagenen Verifizierungsversuchen gesperrt. |
Jede Familie teilt sich einen kleinen Satz von gemeinsamen Feldern. Jede Familie ergänzt dann eigene Kontextfelder sowie eine ereignisspezifische Nutzlast.
Gemeinsame Felder
In jeder Auslieferung unabhängig von der Familie enthalten:
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| hookId | string | Die Webhook-Konfigurations-ID in Logto. | |
| event | string | Das Ereignis, das diese Auslieferung ausgelöst hat. | |
| createdAt | string | Die Erstellungszeit der Nutzlast im ISO 8601-Format. | |
| userAgent | string | ✅ | Der User-Agent der auslösenden Anfrage. |
Jede Familie enthält außerdem die IP-Adresse der auslösenden Anfrage, unter dem Feldnamen userIp für User-Flow-Ereignisse und ip für Data-Mutation- und Exception-Ereignisse. Die Semantik ist identisch; der historische Namensunterschied bleibt aus Gründen der Rückwärtskompatibilität erhalten.
User-Flow-Ereignis-Nutzlasten
Ereignisse: PostRegister, PostSignIn, PostResetPassword.
Wird ausgelöst, wenn ein Benutzer einen Anmelde-, Anmelde- oder Passwort-Reset-Flow abschließt, der von der Experience API verarbeitet wird. Zusätzlich zu den gemeinsamen Feldern enthält der Body:
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | Der User-Flow-Ereignistyp. Entspricht jeweils PostSignIn / PostRegister / PostResetPassword. Der Feldname behält die historische "interaction"-Benennung. | |
| sessionId | string | ✅ | Die Session-ID (nicht Interaction-ID) für dieses Ereignis, falls zutreffend. |
| userIp | string | ✅ | Die IP-Adresse der auslösenden Anfrage. |
| userId | string | ✅ | Die Benutzer-ID, die mit diesem Ereignis verknüpft ist, falls zutreffend. |
| user | UserEntity | ✅ | Die Benutzer-Entität, die mit diesem Ereignis verknüpft ist, falls zutreffend. |
| applicationId | string | ✅ | Die Anwendungs-ID, die mit diesem Ereignis verknüpft ist, falls zutreffend. |
| application | ApplicationEntity | ✅ | Die Anwendungs-Entität, die mit diesem Ereignis verknüpft ist, falls zutreffend. |
Entitätsstrukturen
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;
};
Siehe Benutzer und Anwendungen für die vollständige Feldreferenz.
Data-Mutation-Ereignis-Nutzlasten
Ereignisse: alle Ereignisse unter User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*. Siehe Webhooks-Ereignisse → Data-Mutation-Webhook-Ereignisse für den vollständigen Katalog.
Der Body enthält immer:
- Die gemeinsamen Felder.
- Ein
ip-Feld, die IP-Adresse der auslösenden Anfrage (optional, vorhanden wenn bekannt). - Einen API-Kontext, der beschreibt, wie die Änderung ausgelöst wurde. Der Kontext ist eine von zwei Varianten, abhängig von der Auslösequelle:
- Experience API-Kontext, wenn die Änderung aus einem benutzerorientierten Flow stammt.
- Management API-Kontext, wenn die Änderung durch einen direkten Management API-Aufruf ausgelöst wurde.
- Eine ereignisspezifische Nutzlast: die betroffene Entität in
dataund (bei einigen Ereignissen) zusätzliche Top-Level-Felder. Siehe ereignisspezifische Daten-Nutzlasten.
Experience API-Kontextfelder
Vorhanden, wenn die Änderung durch einen benutzerorientierten Flow auf der Experience API ausgelöst wurde, z. B. User.Created während der Registrierung oder User.Data.Updated bei Profilaktualisierungen.
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | ✅ | Der User-Flow-Ereignistyp, der die Änderung ausgelöst hat. Feldname behält die historische "interaction"-Benennung. |
| sessionId | string | ✅ | Die Session-ID (nicht Interaction-ID) für dieses Ereignis, falls zutreffend. |
| applicationId | string | ✅ | Die Anwendungs-ID, falls zutreffend. |
| application | ApplicationEntity | ✅ | Die Anwendungs-Entität, falls zutreffend. |
Management API-Kontextfelder
Vorhanden, wenn die Änderung durch einen Management API-Aufruf ausgelöst wurde.
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| path | string | ✅ | Der Pfad des API-Aufrufs, der diesen Webhook ausgelöst hat. |
| method | string | ✅ | Die HTTP-Methode des API-Aufrufs. |
| status | number | ✅ | Der Antwort-Statuscode des API-Aufrufs. |
| params | object | ✅ | Die koa path params des API-Aufrufs. |
| matchedRoute | string | ✅ | Die koa matched route. Logto verwendet dieses Feld, um aktivierte Webhook-Ereignisfilter zuzuordnen. |
Ereignisspezifische Daten-Nutzlasten
Jedes Data-Mutation-Ereignis enthält ein Top-Level-data-Feld mit der betroffenen Entität oder null, wenn die Änderung nicht als einzelne Entität zusammengefasst werden kann (Lösch- und Mitgliedschaftsereignisse). Einige Ereignisse enthalten außerdem ereignisspezifische Top-Level-Felder außerhalb von data; Organization.Membership.Updated ist ein solcher Fall, unten dokumentiert.
Benutzerereignisse
| Ereignis | Feld | Typ | Optional | Hinweise |
|---|---|---|---|---|
| User.Created | data | UserEntity | Die erstellte Benutzer-Entität. | |
| User.Data.Updated | data | UserEntity | Die aktualisierte Benutzer-Entität. | |
| User.Deleted | data | null | / |
Rollenereignisse
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;
};
| Ereignis | Feld | Typ | Optional | Hinweise |
|---|---|---|---|---|
| Role.Created | data | Role | Die erstellte Rollen-Entität. | |
| Role.Data.Updated | data | Role | Die aktualisierte Rollen-Entität. | |
| Role.Deleted | data | null | / | |
| Role.Scopes.Updated | data | Scope[] | Die aktualisierten Berechtigungen, die der Rolle zugewiesen sind. | |
| Role.Scopes.Updated | roleId | string | ✅ | Die Rollen-ID, der Berechtigungen zugewiesen werden. (Nur verfügbar, wenn das Ereignis durch das Erstellen einer Rolle mit zugewiesenen Berechtigungen ausgelöst wurde.) |
Berechtigungs- (Scope-)Ereignisse
| Ereignis | Feld | Typ | Optional | Hinweise |
|---|---|---|---|---|
| Scope.Created | data | Scope | Die erstellte Berechtigungs-Entität. | |
| Scope.Data.Updated | data | Scope | Die aktualisierte Berechtigungs-Entität. | |
| Scope.Deleted | data | null | / |
Organisationsereignisse
type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
| Ereignis | Feld | Typ | Optional | Hinweise |
|---|---|---|---|---|
| Organization.Created | data | Organization | Die erstellte Organisations-Entität. | |
| Organization.Data.Updated | data | Organization | Die aktualisierte Organisations-Entität. | |
| Organization.Deleted | data | null | / | |
| Organization.Membership.Updated | data | null | / | Die Änderung wird durch optionale Top-Level-Delta-Arrays beschrieben. Siehe Organization.Membership.Updated-Nutzlast unten. |
Organization.Membership.Updated-Nutzlast
Zusätzlich zu den gemeinsamen Feldern und den API-Kontextfeldern, die für die Auslösequelle gelten (Management API-Kontext für Management API-Routen, Experience API-Kontext für Just-in-Time-Bereitstellung), enthält das Ereignis Organization.Membership.Updated eine organizationId sowie optionale Delta-Arrays auf Top-Level-Ebene der Nutzlast (neben event, createdAt usw., nicht innerhalb von data, das für dieses Ereignis immer null ist).
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| organizationId | string | Die Organisation, deren Mitgliedschaft sich geändert hat. | |
| addedUserIds | string[] | ✅ | Benutzer-IDs, die durch diesen Auslöser neu hinzugefügt wurden. Fehlt, wenn keine Benutzer hinzugefügt wurden oder die Änderung keine Benutzer betrifft. |
| removedUserIds | string[] | ✅ | Benutzer-IDs, die durch diesen Auslöser entfernt wurden. Fehlt, wenn keine Benutzer entfernt wurden. |
| addedApplicationIds | string[] | ✅ | Anwendungs-IDs, die neu hinzugefügt wurden. Fehlt, wenn keine Anwendungen hinzugefügt wurden oder die Änderung keine Anwendungen betrifft. |
| removedApplicationIds | string[] | ✅ | Anwendungs-IDs, die entfernt wurden. Fehlt, wenn keine Anwendungen entfernt wurden. |
Die vier Delta-Arrays sind optional und additiv: Sie verändern nicht die bestehende Nutzlaststruktur für Konsumenten, die sie nicht erwarten, und das Legacy-Feld data: null wird weiterhin unverändert ausgegeben.
Auslöser und welche Delta-Felder sie ausgeben können
| Auslöser | Mögliche Delta-Felder |
|---|---|
POST /organizations/:id/users | addedUserIds |
PUT /organizations/:id/users | addedUserIds, removedUserIds |
DELETE /organizations/:id/users/:userId | removedUserIds |
POST /organizations/:id/applications | addedApplicationIds |
PUT /organizations/:id/applications | addedApplicationIds, removedApplicationIds |
DELETE /organizations/:id/applications/:applicationId | removedApplicationIds |
PUT /organization-invitations/:id/status (Accepted) | addedUserIds |
| Just-in-Time-Bereitstellung beim Hinzufügen des Benutzers zu einer neuen Organisation | addedUserIds |
Leere Deltas werden ausgelassen (abwesend ≠ leere Änderung)
Leere Delta-Arrays werden vollständig aus der Nutzlast ausgelassen. Zum Beispiel erzeugt ein PUT /organizations/:id/users, das die Mitgliedschaftsmenge durch die bestehende Menge ersetzt, keine echte Änderung, und die Nutzlast reduziert sich auf nur { organizationId } mit allen vier Delta-Feldern abwesend. Gleiches gilt für das erneute Hinzufügen eines bestehenden Mitglieds und das erneute Akzeptieren einer Einladung durch einen Benutzer, der bereits Mitglied ist.
Konsumenten müssen ein fehlendes Feld als "keine Änderung auf dieser Seite" behandeln, nicht als "leere Änderung".
Pro-Array-Limit (stilles Abschneiden)
Jedes Delta-Array ist auf 5000 Einträge begrenzt. Wenn ein einzelner Management API-Aufruf mehr als 5000 Benutzer (oder Anwendungen) in einer Operation hinzufügt oder entfernt, wird das entsprechende Delta-Array stillschweigend auf die ersten 5000 Einträge gekürzt. Es gibt keinen Marker in der Nutzlast, der anzeigt, dass das Limit erreicht wurde.
Wenn deine Anwendung administrative Massenoperationen durchführt, die plausibel mehr als 5000 Mitglieder in einem Aufruf betreffen können, behandle ein Array mit genau 5000 Einträgen als Signal, die autoritative Mitgliedschaft über die Management API abzugleichen:
GET /organizations/:id/users: vollständige Benutzer-Mitgliedschaft.GET /organizations/:id/applications: vollständige Anwendungs-Mitgliedschaft.
Dies folgt demselben Muster wie das push-Ereignis von GitHub, das commits auf 20 Einträge begrenzt und Konsumenten auf die Compare-API für die vollständige Liste verweist.
No-Op-Ereignisse überspringen
Um No-Op-Auslieferungen (Ereignisse ohne Delta-Felder) auf der Konsumentenseite zu überspringen, filtere nach Vorhandensein von Delta-Arrays:
if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// echte Mitgliedschaftsänderung, verarbeite sie
}
?.length ist sowohl für undefined als auch für [] falsch, sodass derselbe Ausdruck robust ist, egal ob das Feld fehlt oder (in einer hypothetischen Zukunft) als leeres Array ausgegeben wird.
Beispiel-Nutzlasten
Benutzer hinzufügen (POST /organizations/:id/users):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_001"]
}
Benutzermitgliedschaft ersetzen (PUT /organizations/:id/users):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_002"],
"removedUserIds": ["u_001"]
}
Benutzer entfernen (DELETE /organizations/:id/users/:userId):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_001"]
}
Anwendung hinzufügen (POST /organizations/:id/applications):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedApplicationIds": ["app_xyz"]
}
Bestehendes Mitglied erneut hinzufügen, No-Op-PUT oder erneutes Akzeptieren einer bereits bestehenden Einladung (keine echte Änderung):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc"
}
Massenoperation, die das 5000er-Limit erreicht (still gekürzt):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_0001", "u_0002", "/* … genau 5000 Einträge insgesamt */"]
}
Das Erkennen eines Arrays mit genau 5000 Einträgen sollte einen Abgleich per GET /organizations/:id/users (oder /applications) auslösen.
Organisationsrollen-Ereignisse
type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
| Ereignis | Feld | Typ | Optional | Hinweise |
|---|---|---|---|---|
| OrganizationRole.Created | data | OrganizationRole | Die erstellte Organisationsrollen-Entität. | |
| OrganizationRole.Data.Updated | data | OrganizationRole | Die aktualisierte Organisationsrollen-Entität. | |
| OrganizationRole.Deleted | data | null | / | |
| OrganizationRole.Scopes.Updated | data | null | / | |
| OrganizationRole.Scopes.Updated | organizationRoleId | string | ✅ | Die Rollen-ID, der Berechtigungen zugewiesen werden. (Nur verfügbar, wenn das Ereignis durch das Erstellen einer Rolle mit zugewiesenen Berechtigungen ausgelöst wurde.) |
Organisationsberechtigungs- (Scope-)Ereignisse
| Ereignis | Feld | Typ | Optional | Hinweise |
|---|---|---|---|---|
| OrganizationScope.Created | data | OrganizationScope | Die erstellte Organisationsberechtigungs-Entität. | |
| OrganizationScope.Data.Updated | data | OrganizationScope | Die aktualisierte Organisationsberechtigungs-Entität. | |
| OrganizationScope.Deleted | data | null | / |
Ausnahme-Ereignis-Nutzlasten
Ereignisse: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded.
Wird bei Sicherheitsvorfällen ausgelöst, z. B. wenn ein Konto nach aufeinanderfolgenden fehlgeschlagenen Verifizierungsversuchen gesperrt wird oder Grants entfernt werden, weil das gleichzeitige Geräte-Limit einer App überschritten wurde.
Jedes Ausnahme-Ereignis enthält die gemeinsamen Felder und ein ip-Feld (gleiche Struktur wie bei Data-Mutation-Ereignissen). Die übrigen Felder hängen vom Ereignis ab.
Identifier.Lockout
Stammt aus einem benutzerorientierten Flow, daher enthält der Body auch die Experience API-Kontextfelder sowie:
enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| type | SignInIdentifier | Der Identifikatortyp des Benutzers, z. B. E-Mail, Telefon oder Benutzername. | |
| value | string | Der Identifikatorwert des Benutzers, der die Sperrung ausgelöst hat. |
Message.RateLimited
Stammt aus einem benutzerorientierten Flow, daher enthält der Body auch die Experience API-Kontextfelder sowie:
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| action | string | Die rate-limitierte Aktion, z. B. VerificationCodeSend. | |
| recipient | string | Die E-Mail-Adresse oder Telefonnummer, die das Send-Rate-Limit erreicht hat. |
Grant.LimitExceeded
Wird ausgelöst, wenn eine erfolgreiche Autorisierung einen Benutzer über das maximal zulässige gleichzeitige authentifizierte Geräte-Limit (maxAllowedGrants) einer App hinausbringt und Logto deren älteste Grants für diese App widerruft.
Dieses Ereignis wird vom OIDC-Autorisierungsendpunkt und nicht von der Experience API ausgelöst, daher enthält es nicht interactionEvent oder sessionId. Neben den gemeinsamen Feldern und ip enthält der Body:
| Feld | Typ | Optional | Hinweise |
|---|---|---|---|
| userId | string | Der Benutzer, dessen Grants widerrufen wurden. | |
| applicationId | string | Die Anwendung, deren maxAllowedGrants-Limit überschritten wurde. | |
| application | ApplicationEntity | ✅ | Die Anwendungs-Entität. Fehlt, wenn die Anwendung zum Zeitpunkt der Auslieferung nicht aufgelöst werden kann. |
| maxAllowedGrants | number | Das zum Zeitpunkt des Ereignisses konfigurierte Limit auf der Anwendung. | |
| preRevocationActiveGrantCount | number | Die Anzahl aktiver Grants, die der Benutzer für diese Anwendung vor dem Widerruf hielt, einschließlich des gerade ausgestellten. | |
| revokedGrantIds | string[] | Die IDs der tatsächlich widerrufenen Grants, älteste zuerst. |
Beispiel-Nutzlast:
{
"hookId": "hook_abc",
"event": "Grant.LimitExceeded",
"createdAt": "2024-01-01T00:00:00.000Z",
"ip": "192.168.0.1",
"userAgent": "Mozilla/5.0",
"userId": "u_001",
"applicationId": "app_xyz",
"application": {
"id": "app_xyz",
"type": "SPA",
"name": "My app",
"description": "My app description"
},
"maxAllowedGrants": 2,
"preRevocationActiveGrantCount": 3,
"revokedGrantIds": ["grant_001"]
}
Hinweise zur Auslieferung:
- Die widerrufenen Grant-Datensätze werden im Rahmen des Widerrufs zerstört, sodass die IDs in
revokedGrantIdsnicht mehr über die Grant-Listing-Endpunkte oder die Konsole aufgelöst werden können — diese geben nur aktive Grants zurück. Behandle die Nutzlast als Datensatz und speichere die IDs auf deiner Seite, wenn du sie später benötigst. - Das Ereignis wird nur ausgelöst, wenn mindestens ein Grant tatsächlich widerrufen wurde. Eine Autorisierung, die innerhalb des Limits bleibt, erzeugt kein Ereignis.
- Es wird bei jeder Autorisierung ausgelöst, die das Limit überschreitet, sodass ein Benutzer, der sich wiederholt von mehr Geräten als erlaubt anmeldet, pro Entfernung ein Ereignis erhält.
- Die Zustellung erfolgt nach dem Fire-and-Forget-Prinzip: Ein langsamer oder fehlerhafter Endpunkt blockiert oder verhindert niemals die Autorisierung des Benutzers. Fehlgeschlagene Auslieferungen werden wie jeder andere Webhook im Audit-Log aufgezeichnet.