Aller au contenu principal

Requête Webhooks

Lorsqu'un événement webhook est déclenché, Logto envoie une requête POST à chaque point de terminaison abonné à cet événement. Le catalogue complet des événements se trouve dans Événements Webhooks ; cette page documente la structure de la requête envoyée par Logto.

En-têtes de la requête

CléPersonnalisableRemarques
user-agentLogto (https://logto.io/) par défaut.
content-typeapplication/json par défaut.
logto-signature-sha-256Signature du corps de la requête. Voir sécuriser vos webhooks.

Les en-têtes personnalisables peuvent être remplacés via la configuration secure webhook.

Aperçu du corps de la requête

Le corps est un objet JSON. Sa structure exacte dépend de la famille à laquelle appartient l'événement :

FamilleÉvénementsQuand il est déclenché
Flux utilisateurPostRegister, PostSignIn, PostResetPasswordUn utilisateur termine un flux d'inscription, de connexion ou de réinitialisation de mot de passe géré par l’Experience API.
Mutation de donnéesUser.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*Le modèle de données sous-jacent est modifié par un appel Management API ou un flux utilisateur sur l’Experience API.
ExceptionIdentifier.Lockout, Message.RateLimited, Grant.LimitExceededUn incident de sécurité, par exemple un compte verrouillé après plusieurs tentatives de vérification échouées.

Chaque famille partage un petit ensemble de champs communs. Chaque famille ajoute ensuite ses propres champs de contexte de requête ainsi qu'une charge utile spécifique à l'événement.

Champs communs

Présents dans chaque livraison, quelle que soit la famille :

ChampTypeOptionnelRemarques
hookIdstringL'identifiant de la configuration du webhook dans Logto.
eventstringL'événement qui a déclenché cette livraison.
createdAtstringL'heure de création de la charge utile au format ISO 8601.
userAgentstringLe user-agent de la requête déclenchante.

Chaque famille inclut également l'adresse IP de la requête déclenchante, sous le nom de champ userIp pour les événements de flux utilisateur et ip pour les événements de mutation de données et d'exception. La sémantique est identique ; la différence de nom historique est conservée pour la compatibilité ascendante.

Charges utiles des événements de flux utilisateur

Événements : PostRegister, PostSignIn, PostResetPassword.

Déclenché lorsqu'un utilisateur termine un flux d'inscription, de connexion ou de réinitialisation de mot de passe géré par l’Experience API. En plus des champs communs, le corps contient :

ChampTypeOptionnelRemarques
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'Le type d'événement du flux utilisateur. Correspond à PostSignIn / PostRegister / PostResetPassword respectivement. Le nom du champ conserve la terminologie historique "interaction".
sessionIdstringL’ID de session (et non l’ID d’interaction) pour cet événement, si applicable.
userIpstringL'adresse IP de la requête déclenchante.
userIdstringL’ID utilisateur associé à cet événement, si applicable.
userUserEntityL'entité utilisateur associée à cet événement, si applicable.
applicationIdstringL’ID de l’application associée à cet événement, si applicable.
applicationApplicationEntityL'entité application associée à cet événement, si applicable.

Structures des entités

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;
};

Voir Utilisateurs et Applications pour la référence complète des champs.

Charges utiles des événements de mutation de données

Événements : tout événement sous User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*. Voir Événements Webhooks → Événements de mutation de données pour le catalogue complet.

Le corps contient toujours :

  • Les champs communs.
  • Un champ ip, l'adresse IP de la requête déclenchante (optionnel, présent si connu).
  • Un contexte API décrivant comment la modification a été déclenchée. Le contexte est l'une des deux variantes selon la source du déclencheur :
  • Une charge utile spécifique à l'événement : l'entité affectée dans data et (pour certains événements) des champs supplémentaires au niveau supérieur. Voir charges utiles spécifiques à l'événement.

Champs de contexte Experience API

Présents lorsque la modification a été déclenchée par un flux utilisateur sur l’Experience API, par exemple User.Created lors de l'inscription ou User.Data.Updated lors de la mise à jour du profil.

ChampTypeOptionnelRemarques
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'Le type d'événement du flux utilisateur ayant produit la modification. Le nom du champ conserve la terminologie historique "interaction".
sessionIdstringL’ID de session (et non l’ID d’interaction) pour cet événement, si applicable.
applicationIdstringL’ID de l’application, si applicable.
applicationApplicationEntityL'entité application, si applicable.

Champs de contexte Management API

Présents lorsque la modification a été déclenchée par un appel Management API.

ChampTypeOptionnelRemarques
pathstringLe chemin de l'appel API ayant déclenché ce webhook.
methodstringLa méthode HTTP de l'appel API.
statusnumberLe code de statut de la réponse de l'appel API.
paramsobjectLes paramètres de chemin koa de l'appel API.
matchedRoutestringLa route koa correspondante. Logto utilise ce champ pour filtrer les événements webhook activés.

Charges utiles spécifiques à l'événement

Chaque événement de mutation de données inclut un champ de niveau supérieur data contenant l'entité affectée, ou null lorsque la modification ne peut pas être résumée par une seule entité (événements de suppression et d'appartenance). Certains événements incluent également des champs spécifiques à l'événement au niveau supérieur ; Organization.Membership.Updated en est un exemple, documenté ci-dessous.

Événements utilisateur

ÉvénementChampTypeOptionnelRemarques
User.CreateddataUserEntityL'entité utilisateur créée.
User.Data.UpdateddataUserEntityL'entité utilisateur mise à jour.
User.Deleteddatanull/

Événements rôle

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;
};
ÉvénementChampTypeOptionnelRemarques
Role.CreateddataRoleL'entité rôle créée.
Role.Data.UpdateddataRoleL'entité rôle mise à jour.
Role.Deleteddatanull/
Role.Scopes.UpdateddataScope[]Les portées mises à jour attribuées au rôle.
Role.Scopes.UpdatedroleIdstringL’ID du rôle auquel les portées sont attribuées. (Disponible uniquement si l’événement a été déclenché lors de la création d’un rôle avec des portées pré-attribuées.)

Événements permission (portée)

ÉvénementChampTypeOptionnelRemarques
Scope.CreateddataScopeL'entité portée créée.
Scope.Data.UpdateddataScopeL'entité portée mise à jour.
Scope.Deleteddatanull/

Événements organisation

type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
ÉvénementChampTypeOptionnelRemarques
Organization.CreateddataOrganizationL'entité organisation créée.
Organization.Data.UpdateddataOrganizationL'entité organisation mise à jour.
Organization.Deleteddatanull/
Organization.Membership.Updateddatanull/La modification est décrite par des tableaux delta optionnels au niveau supérieur. Voir payload Organization.Membership.Updated ci-dessous.
Payload Organization.Membership.Updated

En plus des champs communs et des champs de contexte API applicables à la source du déclencheur (contexte Management API pour les routes Management API, contexte Experience API pour l’approvisionnement just-in-time), l’événement Organization.Membership.Updated contient un organizationId ainsi que des tableaux delta optionnels au niveau supérieur de la charge utile (à côté de event, createdAt, etc., pas dans data, qui est toujours null pour cet événement).

ChampTypeOptionnelRemarques
organizationIdstringL'organisation dont l'appartenance a changé.
addedUserIdsstring[]Les IDs utilisateurs nouvellement ajoutés par ce déclencheur. Omissible si aucun utilisateur n'a été ajouté, ou si le déclencheur n'affecte pas l'appartenance utilisateur.
removedUserIdsstring[]Les IDs utilisateurs supprimés par ce déclencheur. Omissible si aucun utilisateur n'a été supprimé.
addedApplicationIdsstring[]Les IDs applications nouvellement ajoutées. Omissible si aucune application n'a été ajoutée, ou si le déclencheur n'affecte pas l'appartenance application.
removedApplicationIdsstring[]Les IDs applications supprimées. Omissible si aucune application n'a été supprimée.

Les quatre tableaux delta sont optionnels et additifs : ils ne modifient pas la structure existante de la charge utile pour les consommateurs qui ne les attendent pas, et le champ hérité data: null est toujours émis sans changement.

Déclencheurs et champs delta possibles
DéclencheurChamps delta possibles
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
Approvisionnement just-in-time lors de l’ajout de l’utilisateur à une nouvelle organisationaddedUserIds
Les deltas vides sont omis (absent ≠ changement vide)

Les tableaux delta vides sont totalement omis de la charge utile. Par exemple, un PUT /organizations/:id/users qui remplace l'ensemble d'appartenance par l'ensemble existant ne produit aucun changement réel, et la charge utile se réduit à { organizationId } avec les quatre champs delta absents. Il en va de même pour la réadmission d'un membre existant et la réacceptation d'une invitation par un utilisateur déjà membre.

Les consommateurs doivent traiter un champ manquant comme "aucun changement de ce côté", et non comme "un changement vide".

Limite par tableau (troncature silencieuse)

Chaque tableau delta est limité à 5000 entrées. Lorsqu'un seul appel Management API ajoute ou supprime plus de 5000 utilisateurs (ou applications) en une opération, le tableau delta correspondant est silencieusement tronqué à ses 5000 premières entrées. Il n'y a aucun indicateur dans la charge utile qu'une limite a été atteinte.

Si votre application effectue des opérations administratives de masse susceptibles d'affecter plus de 5000 membres en un seul appel, considérez un tableau de exactement 5000 entrées comme un signal pour réconcilier l'appartenance via la Management API :

  • GET /organizations/:id/users : appartenance utilisateur complète.
  • GET /organizations/:id/applications : appartenance application complète.

Cela suit le même modèle que l'événement push de GitHub, qui limite commits à 20 entrées et oriente les consommateurs vers l’API de comparaison pour la liste complète.

Ignorer les événements sans effet

Pour ignorer côté consommateur les livraisons sans effet (événements sans champs delta), filtrez sur la présence des tableaux delta :

if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// changement réel d'appartenance, à traiter
}

?.length est falsy pour undefined et [], donc le même prédicat est robuste que le champ soit absent ou (dans un futur hypothétique) émis comme un tableau vide.

Exemples de charges utiles

Ajouter un utilisateur (POST /organizations/:id/users) :

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

Remplacer l'ensemble d'appartenance utilisateur (PUT /organizations/:id/users) :

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

Supprimer un utilisateur (DELETE /organizations/:id/users/:userId) :

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

Ajouter une application (POST /organizations/:id/applications) :

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

Réadmission d'un membre existant, PUT sans effet, ou réacceptation d'une invitation déjà membre (aucun changement réel) :

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

Opération de masse atteignant la limite de 5000 (troncature silencieuse) :

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_0001", "u_0002", "/* … exactement 5000 entrées au total */"]
}

Voir un tableau de exactement 5000 entrées doit inciter à une réconciliation via GET /organizations/:id/users (ou /applications).

Événements rôle d'organisation

type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
ÉvénementChampTypeOptionnelRemarques
OrganizationRole.CreateddataOrganizationRoleL'entité rôle d'organisation créée.
OrganizationRole.Data.UpdateddataOrganizationRoleL'entité rôle d'organisation mise à jour.
OrganizationRole.Deleteddatanull/
OrganizationRole.Scopes.Updateddatanull/
OrganizationRole.Scopes.UpdatedorganizationRoleIdstringL’ID du rôle auquel les portées sont attribuées. (Disponible uniquement si l’événement a été déclenché lors de la création d’un rôle avec des portées pré-attribuées.)

Événements permission (portée) d'organisation

ÉvénementChampTypeOptionnelRemarques
OrganizationScope.CreateddataOrganizationScopeL'entité portée d'organisation créée.
OrganizationScope.Data.UpdateddataOrganizationScopeL'entité portée d'organisation mise à jour.
OrganizationScope.Deleteddatanull/

Charges utiles des événements d'exception

Événements : Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded.

Déclenché lors d'incidents de sécurité, par exemple un compte verrouillé après plusieurs tentatives de vérification échouées, ou des autorisations révoquées parce que la limite d'appareils connectés simultanément d'une application a été dépassée.

Chaque événement d'exception contient les champs communs et un champ ip (même structure que les événements de mutation de données). Les champs restants dépendent de l'événement.

Identifier.Lockout

Provenant d'un flux utilisateur, le corps contient également les champs de contexte Experience API, ainsi que :

enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
ChampTypeOptionnelRemarques
typeSignInIdentifierLe type d'identifiant utilisateur, par exemple email, téléphone ou nom d'utilisateur.
valuestringLa valeur de l'identifiant utilisateur ayant déclenché le verrouillage.

Message.RateLimited

Provenant d'un flux utilisateur, le corps contient également les champs de contexte Experience API, ainsi que :

ChampTypeOptionnelRemarques
actionstringL'action limitée en fréquence, par exemple VerificationCodeSend.
recipientstringL'adresse e-mail ou le numéro de téléphone ayant atteint la limite de fréquence d'envoi.

Grant.LimitExceeded

Déclenché lorsqu'une autorisation réussie fait dépasser à un utilisateur la limite maximale d'appareils authentifiés simultanément par application (maxAllowedGrants) et que Logto révoque ses autorisations les plus anciennes pour cette application.

Cet événement est émis depuis le point de terminaison d'autorisation OIDC plutôt que depuis l’Experience API, il ne contient donc pas interactionEvent ni sessionId. En plus des champs communs et de ip, le corps contient :

ChampTypeOptionnelRemarques
userIdstringL'utilisateur dont les autorisations ont été révoquées.
applicationIdstringL'application dont la limite maxAllowedGrants a été dépassée.
applicationApplicationEntityL'entité application. Omissible si l'application ne peut pas être résolue au moment de la livraison.
maxAllowedGrantsnumberLa limite configurée sur l'application lors du déclenchement de l'événement.
preRevocationActiveGrantCountnumberLe nombre d'autorisations actives détenues par l'utilisateur pour cette application avant la révocation, y compris celle qui vient d'être émise.
revokedGrantIdsstring[]Les IDs des autorisations effectivement révoquées, des plus anciennes aux plus récentes.

Exemple de charge utile :

{
"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"]
}

Notes de livraison :

  • Les enregistrements d'autorisations révoquées sont détruits lors de la révocation, donc les IDs dans revokedGrantIds ne sont plus accessibles via les points de terminaison de liste des autorisations ou la Console — ceux-ci ne retournent que les autorisations actives. Considérez la charge utile comme l'enregistrement, et conservez les IDs de votre côté si vous en avez besoin plus tard.
  • L'événement n'est déclenché que lorsqu'au moins une autorisation a été effectivement révoquée. Une autorisation qui reste dans la limite ne produit aucun événement.
  • Il est déclenché à chaque autorisation qui dépasse la limite, donc un utilisateur se connectant à plusieurs reprises depuis plus d'appareils que permis produira un événement par éviction.
  • L'envoi est fire-and-forget : un point de terminaison lent ou défaillant ne bloque ni n'échoue jamais l'autorisation de l'utilisateur. Les livraisons échouées sont enregistrées dans les journaux d'audit comme tout autre webhook.