Saltar al contenido principal

Solicitud de Webhooks

Cuando se dispara un evento de webhook, Logto envía una solicitud POST a cada endpoint suscrito a dicho evento. El catálogo completo de eventos se encuentra en Eventos de Webhooks; en esta página se documenta la estructura de la solicitud que entrega Logto.

Encabezados de la solicitud

ClavePersonalizableNotas
user-agentLogto (https://logto.io/) por defecto.
content-typeapplication/json por defecto.
logto-signature-sha-256Firma del cuerpo de la solicitud. Consulta asegura tus webhooks.

Los encabezados personalizables pueden ser sobrescritos mediante la configuración de webhook seguro.

Resumen del cuerpo de la solicitud

El cuerpo es un objeto JSON. Su forma exacta depende de a qué familia pertenezca el evento:

FamiliaEventosCuándo se dispara
Flujo de usuarioPostRegister, PostSignIn, PostResetPasswordUn usuario completa un flujo de registro, inicio de sesión o restablecimiento de contraseña gestionado por la Experience API.
Mutación de datosUser.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*El modelo de datos subyacente es modificado por una llamada a la Management API o un flujo de usuario en la Experience API.
ExcepciónIdentifier.Lockout, Message.RateLimited, Grant.LimitExceededUn incidente de seguridad, por ejemplo, una cuenta bloqueada tras intentos fallidos consecutivos de verificación.

Cada familia comparte un pequeño conjunto de campos comunes. Luego, cada familia añade sus propios campos de contexto de solicitud y un payload específico del evento.

Campos comunes

Presentes en cada entrega, independientemente de la familia:

CampoTipoOpcionalNotas
hookIdstringEl identificador de configuración del webhook en Logto.
eventstringEl evento que disparó esta entrega.
createdAtstringLa hora de creación del payload en formato ISO 8601.
userAgentstringEl user-agent de la solicitud que disparó el evento.

Cada familia también incluye la dirección IP de la solicitud que disparó el evento, bajo el nombre de campo userIp para eventos de flujo de usuario y ip para eventos de mutación de datos y de excepción. La semántica es idéntica; la diferencia de nombre se mantiene por compatibilidad hacia atrás.

Payloads de eventos de flujo de usuario

Eventos: PostRegister, PostSignIn, PostResetPassword.

Se dispara cuando un usuario completa un flujo de registro, inicio de sesión o restablecimiento de contraseña gestionado por la Experience API. Además de los campos comunes, el cuerpo incluye:

CampoTipoOpcionalNotas
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'El tipo de evento de flujo de usuario. Corresponde a PostSignIn / PostRegister / PostResetPassword respectivamente. El nombre del campo mantiene la denominación histórica "interaction".
sessionIdstringEl ID de sesión (no el ID de interacción) para este evento, si aplica.
userIpstringLa dirección IP de la solicitud que disparó el evento.
userIdstringEl ID de usuario asociado a este evento, si aplica.
userUserEntityLa entidad de usuario asociada a este evento, si aplica.
applicationIdstringEl ID de la aplicación asociada a este evento, si aplica.
applicationApplicationEntityLa entidad de la aplicación asociada a este evento, si aplica.

Formatos de entidad

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

Consulta Usuarios y Aplicaciones para la referencia completa de campos.

Payloads de eventos de mutación de datos

Eventos: todos los eventos bajo User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*. Consulta Eventos de Webhooks → Eventos de mutación de datos para el catálogo completo.

El cuerpo siempre incluye:

  • Los campos comunes.
  • Un campo ip, la dirección IP de la solicitud que disparó el evento (opcional, presente cuando se conoce).
  • Un contexto de API que describe cómo se disparó el cambio. El contexto es una de dos variantes dependiendo de la fuente del disparador:
  • Un payload específico del evento: la entidad afectada en data y (para algunos eventos) campos adicionales de nivel superior. Consulta payloads de datos específicos del evento.

Campos de contexto de Experience API

Presentes cuando el cambio fue disparado por un flujo orientado al usuario en la Experience API, por ejemplo User.Created durante el registro o User.Data.Updated durante actualizaciones de perfil.

CampoTipoOpcionalNotas
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'El tipo de evento de flujo de usuario que produjo el cambio. El nombre del campo mantiene la denominación histórica "interaction".
sessionIdstringEl ID de sesión (no el ID de interacción) para este evento, si aplica.
applicationIdstringEl ID de la aplicación, si aplica.
applicationApplicationEntityLa entidad de la aplicación, si aplica.

Campos de contexto de Management API

Presentes cuando el cambio fue disparado por una llamada a la Management API.

CampoTipoOpcionalNotas
pathstringLa ruta de la llamada API que disparó este webhook.
methodstringEl método HTTP de la llamada API.
statusnumberEl código de estado de la respuesta de la llamada API.
paramsobjectLos parámetros de ruta koa de la llamada API.
matchedRoutestringLa ruta coincidente de koa. Logto usa este campo para coincidir con los filtros de eventos de webhook habilitados.

Payloads de datos específicos del evento

Cada evento de mutación de datos incluye un campo de nivel superior data que contiene la entidad afectada, o null cuando el cambio no puede resumirse como una sola entidad (eventos de eliminación y membresía). Algunos eventos también incluyen campos de nivel superior específicos del evento además de data; Organization.Membership.Updated es uno de estos casos, documentado a continuación.

Eventos de usuario

EventoCampoTipoOpcionalNotas
User.CreateddataUserEntityLa entidad de usuario creada.
User.Data.UpdateddataUserEntityLa entidad de usuario actualizada.
User.Deleteddatanull/

Eventos de rol

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;
};
EventoCampoTipoOpcionalNotas
Role.CreateddataRoleLa entidad de rol creada.
Role.Data.UpdateddataRoleLa entidad de rol actualizada.
Role.Deleteddatanull/
Role.Scopes.UpdateddataScope[]Los alcances actualizados asignados al rol.
Role.Scopes.UpdatedroleIdstringEl ID del rol al que se asignan los alcances. (Solo disponible cuando el evento fue disparado al crear un rol con alcances preasignados.)

Eventos de permiso (Scope)

EventoCampoTipoOpcionalNotas
Scope.CreateddataScopeLa entidad de alcance creada.
Scope.Data.UpdateddataScopeLa entidad de alcance actualizada.
Scope.Deleteddatanull/

Eventos de organización

type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
EventoCampoTipoOpcionalNotas
Organization.CreateddataOrganizationLa entidad de organización creada.
Organization.Data.UpdateddataOrganizationLa entidad de organización actualizada.
Organization.Deleteddatanull/
Organization.Membership.Updateddatanull/El cambio se describe mediante arreglos delta opcionales de nivel superior. Consulta payload de Organization.Membership.Updated abajo.
Payload de Organization.Membership.Updated

Además de los campos comunes y los campos de contexto de API que aplican según la fuente del disparador (contexto de Management API para rutas de Management API, contexto de Experience API para aprovisionamiento just-in-time), el evento Organization.Membership.Updated lleva un organizationId más arreglos delta opcionales en el nivel superior del payload (junto a event, createdAt, etc., no dentro de data, que siempre es null para este evento).

CampoTipoOpcionalNotas
organizationIdstringLa organización cuya membresía cambió.
addedUserIdsstring[]IDs de usuarios añadidos por este disparador. Se omite cuando no se añadieron usuarios, o cuando el disparador no afecta la membresía de usuarios.
removedUserIdsstring[]IDs de usuarios eliminados por este disparador. Se omite cuando no se eliminaron usuarios.
addedApplicationIdsstring[]IDs de aplicaciones añadidas. Se omite cuando no se añadieron aplicaciones, o cuando el disparador no afecta la membresía de aplicaciones.
removedApplicationIdsstring[]IDs de aplicaciones eliminadas. Se omite cuando no se eliminaron aplicaciones.

Los cuatro arreglos delta son opcionales y aditivos: no cambian la forma existente del payload para consumidores que no los esperan, y el campo heredado data: null sigue emitiéndose sin cambios.

Disparadores y qué campos delta pueden emitir
DisparadorCampos delta posibles
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
Aprovisionamiento just-in-time al añadir el usuario a una nueva organizaciónaddedUserIds
Los deltas vacíos se omiten (ausente ≠ cambio vacío)

Los arreglos delta vacíos se omiten completamente del payload. Por ejemplo, un PUT /organizations/:id/users que reemplaza el conjunto de miembros con el mismo conjunto existente no produce ningún cambio real, y el payload se reduce a solo { organizationId } con los cuatro campos delta ausentes. Lo mismo aplica para volver a añadir un miembro existente y para la reaceptación de una invitación por parte de un usuario que ya es miembro.

Los consumidores deben tratar un campo ausente como "sin cambio en ese lado", no como "un cambio vacío".

Límite por arreglo (truncamiento silencioso)

Cada arreglo delta tiene un límite de 5000 entradas. Cuando una sola llamada a la Management API añade o elimina más de 5000 usuarios (o aplicaciones) en una operación, el arreglo delta correspondiente se trunca silenciosamente a sus primeras 5000 entradas. No hay ningún marcador en el payload que indique que se alcanzó el límite.

Si tu aplicación realiza operaciones administrativas masivas que puedan afectar a más de 5000 miembros en una sola llamada, trata un arreglo de exactamente 5000 entradas como una señal para reconciliar la membresía autoritativa mediante la Management API:

  • GET /organizations/:id/users: membresía completa de usuarios.
  • GET /organizations/:id/applications: membresía completa de aplicaciones.

Esto sigue el mismo patrón que el evento push de GitHub, que limita commits a 20 entradas y dirige a los consumidores a la API de comparación para la lista completa.

Omitir eventos sin cambios (no-op)

Para omitir entregas sin cambios (eventos sin campos delta) del lado del consumidor, filtra por la presencia de arreglos delta:

if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// cambio real de membresía, manejarlo
}

?.length es falso tanto para undefined como para [], por lo que el mismo predicado es robusto tanto si el campo está ausente como si (en algún futuro hipotético) se emite como un arreglo vacío.

Ejemplos de payloads

Añadir un usuario (POST /organizations/:id/users):

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

Reemplazar el conjunto de miembros de usuario (PUT /organizations/:id/users):

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

Eliminar un usuario (DELETE /organizations/:id/users/:userId):

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

Añadir una aplicación (POST /organizations/:id/applications):

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

Volver a añadir un miembro existente, PUT sin cambios, o reaceptar una invitación de un usuario que ya es miembro (sin cambio real):

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

Operación masiva que alcanza el límite de 5000 (truncado silenciosamente):

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

Ver un arreglo de exactamente 5000 entradas debe motivar una reconciliación con GET /organizations/:id/users (o /applications).

Eventos de rol de organización

type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
EventoCampoTipoOpcionalNotas
OrganizationRole.CreateddataOrganizationRoleLa entidad de rol de organización creada.
OrganizationRole.Data.UpdateddataOrganizationRoleLa entidad de rol de organización actualizada.
OrganizationRole.Deleteddatanull/
OrganizationRole.Scopes.Updateddatanull/
OrganizationRole.Scopes.UpdatedorganizationRoleIdstringEl ID del rol al que se asignan los alcances. (Solo disponible cuando el evento fue disparado al crear un rol con alcances preasignados.)

Eventos de permiso de organización (scope)

EventoCampoTipoOpcionalNotas
OrganizationScope.CreateddataOrganizationScopeLa entidad de alcance de organización creada.
OrganizationScope.Data.UpdateddataOrganizationScopeLa entidad de alcance de organización actualizada.
OrganizationScope.Deleteddatanull/

Payloads de eventos de excepción

Eventos: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded.

Se dispara en incidentes de seguridad, por ejemplo, una cuenta bloqueada tras intentos fallidos consecutivos de verificación, o concesiones revocadas porque se superó el límite de dispositivos concurrentes autenticados de una aplicación.

Cada evento de excepción lleva los campos comunes y un campo ip (misma estructura que los eventos de mutación de datos). Los campos restantes dependen del evento.

Identifier.Lockout

Se origina en un flujo orientado al usuario, por lo que el cuerpo también incluye los campos de contexto de Experience API, además de:

enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
CampoTipoOpcionalNotas
typeSignInIdentifierEl tipo de identificador del usuario, por ejemplo, email, teléfono o nombre de usuario.
valuestringEl valor del identificador del usuario que disparó el bloqueo.

Message.RateLimited

Se origina en un flujo orientado al usuario, por lo que el cuerpo también incluye los campos de contexto de Experience API, además de:

CampoTipoOpcionalNotas
actionstringLa acción limitada por tasa, por ejemplo, VerificationCodeSend.
recipientstringEl correo electrónico o número de teléfono que alcanzó el límite de envío.

Grant.LimitExceeded

Se dispara cuando una autorización exitosa hace que un usuario supere el límite máximo de dispositivos autenticados concurrentes de una aplicación (maxAllowedGrants) y Logto revoca sus concesiones más antiguas para esa aplicación.

Este evento se emite desde el endpoint de autorización OIDC en lugar de la Experience API, por lo que no lleva interactionEvent ni sessionId. Junto a los campos comunes e ip, el cuerpo incluye:

CampoTipoOpcionalNotas
userIdstringEl usuario cuyas concesiones fueron revocadas.
applicationIdstringLa aplicación cuyo límite maxAllowedGrants fue superado.
applicationApplicationEntityLa entidad de la aplicación. Se omite si la aplicación no puede resolverse en el momento de la entrega.
maxAllowedGrantsnumberEl límite configurado en la aplicación cuando se disparó el evento.
preRevocationActiveGrantCountnumberEl número de concesiones activas que el usuario tenía para esta aplicación antes de la revocación, incluyendo la recién emitida.
revokedGrantIdsstring[]Los IDs de las concesiones que realmente fueron revocadas, de la más antigua a la más reciente.

Ejemplo de payload:

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

Notas de entrega:

  • Los registros de concesiones revocadas se destruyen como parte de la revocación, por lo que los IDs en revokedGrantIds ya no se resuelven a través de los endpoints de listado de concesiones ni en la Consola — estos solo devuelven concesiones activas. Trata el payload como el registro y persiste los IDs en tu lado si los necesitas más adelante.
  • El evento solo se dispara cuando al menos una concesión fue realmente revocada. Una autorización que permanece dentro del límite no produce evento.
  • Se dispara en cada autorización que excede el límite, por lo que un usuario que inicia sesión repetidamente desde más dispositivos de los permitidos produce un evento por cada expulsión.
  • El despacho es fire-and-forget: un endpoint lento o fallido nunca bloquea ni falla la autorización del usuario. Las entregas fallidas se registran en los logs de auditoría como cualquier otro webhook.