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 qui y est abonné. 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-agent✅Logto (https://logto.io/) par défaut.
content-type✅application/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 webhook sécurisé.

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.LimitExceededIncident 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.
deliveryIdstringL’identifiant de cette livraison, unique par webhook et événement et conservé lors des reprises automatiques de la même requête. Voir déduplication des livraisons.
eventstringL’événement qui a déclenché cette livraison.
createdAtstringL’horodatage de création de la charge utile au format ISO 8601.
userAgentstring✅Le 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 est conservée pour la compatibilité ascendante.

Déduplication des livraisons​

Une livraison peut atteindre votre point de terminaison plusieurs fois : Logto réessaie une requête dont le statut de réponse est 500 ou plus, donc une requête que votre point de terminaison a traitée mais à laquelle il a tout de même répondu par un 5xx (par exemple, une passerelle retournant 504 devant un gestionnaire lent) arrive à nouveau. Chaque tentative de la même livraison porte le même deliveryId, tandis qu’un nouvel événement, ou le même événement envoyé à un autre webhook, en reçoit un nouveau. Enregistrez le deliveryId de chaque charge utile que vous avez traitée et ignorez une charge utile dont l’id est déjà connu.

Le deliveryId est également l’id de l’entrée du journal d’audit qui enregistre la livraison, donc une valeur signalée par votre service peut être ouverte depuis les requêtes récentes du webhook dans la Console ou récupérée avec GET /api/logs/:id.

La charge utile de test porte aussi un deliveryId, donc le même gestionnaire peut la traiter, bien que les requêtes de test ne soient pas journalisées.

Charges utiles des événements de flux utilisateur​

Événements : PostRegister, PostSignIn, PostResetPassword.

Déclenchés 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 de flux utilisateur. Correspond à PostSignIn / PostRegister / PostResetPassword respectivement. Le nom du champ conserve l’historique "interaction".
sessionIdstring✅L’ID de session (et non l’ID d’interaction) pour cet événement, si applicable.
userIpstring✅L’adresse IP de la requête déclenchante.
userIdstring✅L’ID utilisateur associé à cet événement, si applicable.
userUserEntity✅L’entité utilisateur associée à cet événement, si applicable.
applicationIdstring✅L’ID de l’application associée à cet événement, si applicable.
applicationApplicationEntity✅L’entité application associée à cet événement, si applicable.

Structures d’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é concerné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 de flux utilisateur ayant produit la modification. Le nom du champ conserve l’historique "interaction".
sessionIdstring✅L’ID de session (et non l’ID d’interaction) pour cet événement, si applicable.
applicationIdstring✅L’ID de l’application, si applicable.
applicationApplicationEntity✅L’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
pathstring✅Le chemin de l’appel API ayant déclenché ce webhook.
methodstring✅La méthode HTTP de l’appel API.
statusnumber✅Le code de statut de la réponse de l’appel API.
paramsobject✅Les paramètres de chemin koa de l’appel API.
matchedRoutestring✅La route koa correspondante. Logto utilise ce champ pour faire correspondre les filtres d’événements webhook activés.

Charges utiles spécifiques à l’événement​

Chaque événement de mutation de données inclut un champ data au niveau supérieur contenant l’entité concerné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 supplémentaires 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.UpdatedroleIdstring✅L’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[]✅IDs des 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[]✅IDs des utilisateurs supprimés par ce déclencheur. Omissible si aucun utilisateur n’a été supprimé.
addedApplicationIdsstring[]✅IDs des 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[]✅IDs des applications supprimées. Omissible si aucune application n’a été supprimée.

Les quatre tableaux delta sont optionnels et additifs : ils ne changent 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 modification.

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 des membres 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 une réadmission d’un membre existant et une 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 tronqué silencieusement à 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 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 d’appartenance réel, à traiter
}

?.length est falsy pour undefined comme pour [], donc le même prédicat est robuste que le champ soit absent ou (dans un futur hypothétique) émis comme 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 des membres (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 à effectuer un GET /organizations/:id/users (ou /applications) pour réconcilier.

Événements de 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.UpdatedorganizationRoleIdstring✅L’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 d’organisation (portée)​

É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és 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 simultanés 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 email 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és d’une 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. Outre les champs communs et 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.
applicationApplicationEntity✅L’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",
"deliveryId": "3nfp8xk1q2z7",
"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 la trace, et conservez les IDs de votre côté si besoin.
  • 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 en échec 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.