Requisição de Webhooks
Quando um evento de webhook é disparado, o Logto envia uma requisição POST para cada endpoint inscrito nele. O catálogo completo de eventos está em Eventos de Webhooks; esta página documenta o formato da requisição entregue pelo Logto.
Cabeçalhos da requisição
| Key | Personalizável | Observações |
|---|---|---|
| user-agent | ✅ | Logto (https://logto.io/) por padrão. |
| content-type | ✅ | application/json por padrão. |
| logto-signature-sha-256 | Assinatura do corpo da requisição. Veja protegendo seus webhooks. |
Cabeçalhos personalizáveis podem ser sobrescritos via a configuração de webhook seguro.
Visão geral do corpo da requisição
O corpo é um objeto JSON. Seu formato exato depende de qual família o evento pertence:
| Família | Eventos | Quando é disparado |
|---|---|---|
| Fluxo do usuário | PostRegister, PostSignIn, PostResetPassword | Um usuário completa um fluxo de cadastro, login ou redefinição de senha tratado pela Experience API. |
| Mutação de dados | User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* | O modelo de dados subjacente é alterado por uma chamada à Management API ou um fluxo de usuário na Experience API. |
| Exceção | Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded | Um incidente de segurança, por exemplo, uma conta bloqueada após tentativas consecutivas de verificação malsucedidas. |
Cada família compartilha um pequeno conjunto de campos comuns. Cada família então adiciona seus próprios campos de contexto de requisição e um payload específico do evento.
Campos comuns
Presentes em toda entrega, independentemente da família:
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| hookId | string | O identificador da configuração do webhook no Logto. | |
| deliveryId | string | O identificador desta entrega, único por webhook e evento e mantido entre as tentativas automáticas da mesma requisição. Veja deduplicação de entregas. | |
| event | string | O evento que disparou esta entrega. | |
| createdAt | string | O horário de criação do payload no formato ISO 8601. | |
| userAgent | string | ✅ | O user-agent da requisição que disparou o evento. |
Cada família também inclui o endereço IP da requisição que disparou o evento, sob o nome de campo userIp para eventos de fluxo do usuário e ip para eventos de mutação de dados e exceção. A semântica é idêntica; a diferença de nome é mantida por compatibilidade retroativa.
Deduplicação de entregas
Uma entrega pode chegar ao seu endpoint mais de uma vez: o Logto tenta novamente uma requisição cuja resposta tem status 500 ou superior, então uma requisição que seu endpoint processou mas ainda respondeu com um 5xx (por exemplo, um gateway retornando 504 na frente de um handler lento) chega novamente. Cada tentativa da mesma entrega carrega o mesmo deliveryId, enquanto um novo evento, ou o mesmo evento enviado para outro webhook, recebe um novo. Registre o deliveryId de cada payload que você processou e ignore um payload cujo id você já conhece.
O deliveryId também é o id do registro de auditoria que grava a entrega, então um valor reportado pelo seu serviço pode ser aberto a partir das requisições recentes do webhook no Console ou buscado com GET /api/logs/:id.
O payload de teste também carrega um deliveryId, então o mesmo handler pode processá-lo, embora requisições de teste não sejam registradas.
Payloads de eventos de fluxo do usuário
Eventos: PostRegister, PostSignIn, PostResetPassword.
Disparados quando um usuário completa um fluxo de cadastro, login ou redefinição de senha tratado pela Experience API. Além dos campos comuns, o corpo carrega:
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | O tipo de evento do fluxo do usuário. Mapeia para PostSignIn / PostRegister / PostResetPassword respectivamente. O nome do campo mantém a nomenclatura histórica "interaction". | |
| sessionId | string | ✅ | O ID da sessão (não o ID de interação) para este evento, se aplicável. |
| userIp | string | ✅ | O endereço IP da requisição que disparou o evento. |
| userId | string | ✅ | O ID do usuário associado a este evento, se aplicável. |
| user | UserEntity | ✅ | A entidade de usuário associada a este evento, se aplicável. |
| applicationId | string | ✅ | O ID do aplicativo associado a este evento, se aplicável. |
| application | ApplicationEntity | ✅ | A entidade de aplicativo associada a este evento, se aplicável. |
Formatos das entidades
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;
};
Veja Usuários e Aplicativos para a referência completa dos campos.
Payloads de eventos de mutação de dados
Eventos: todo evento sob User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*. Veja Eventos de Webhooks → Eventos de webhook de mutação de dados para o catálogo completo.
O corpo sempre carrega:
- Os campos comuns.
- Um campo
ip, o endereço IP da requisição que disparou o evento (opcional, presente quando conhecido). - Um contexto de API descrevendo como a alteração foi disparada. O contexto é uma de duas variantes dependendo da fonte do disparo:
- Contexto da Experience API, quando a alteração veio de um fluxo voltado ao usuário.
- Contexto da Management API, quando a alteração veio de uma chamada direta à Management API.
- Um payload específico do evento: a entidade afetada em
datae (para alguns eventos) campos adicionais no topo. Veja payloads de dados específicos do evento.
Campos de contexto da Experience API
Presentes quando a alteração foi disparada por um fluxo voltado ao usuário na Experience API, por exemplo User.Created durante o cadastro ou User.Data.Updated durante atualizações de perfil.
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | ✅ | O tipo de evento do fluxo do usuário que produziu a alteração. O nome do campo mantém a nomenclatura histórica "interaction". |
| sessionId | string | ✅ | O ID da sessão (não o ID de interação) para este evento, se aplicável. |
| applicationId | string | ✅ | O ID do aplicativo, se aplicável. |
| application | ApplicationEntity | ✅ | A entidade de aplicativo, se aplicável. |
Campos de contexto da Management API
Presentes quando a alteração foi disparada por uma chamada à Management API.
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| path | string | ✅ | O caminho da chamada de API que disparou este webhook. |
| method | string | ✅ | O método HTTP da chamada de API. |
| status | number | ✅ | O código de status da resposta da chamada de API. |
| params | object | ✅ | Os parâmetros de caminho koa da chamada de API. |
| matchedRoute | string | ✅ | A rota koa correspondente. O Logto usa este campo para corresponder filtros de eventos de webhook habilitados. |
Payloads de dados específicos do evento
Todo evento de mutação de dados inclui um campo de topo data carregando a entidade afetada, ou null quando a alteração não pode ser resumida como uma única entidade (eventos de exclusão e de associação). Alguns eventos também incluem campos de topo específicos do evento além de data; Organization.Membership.Updated é um desses casos, documentado abaixo.
Eventos de usuário
| Evento | Campo | Tipo | Opcional | Observações |
|---|---|---|---|---|
| User.Created | data | UserEntity | A entidade de usuário criada. | |
| User.Data.Updated | data | UserEntity | A entidade de usuário atualizada. | |
| User.Deleted | data | null | / |
Eventos de papel
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;
};
| Evento | Campo | Tipo | Opcional | Observações |
|---|---|---|---|---|
| Role.Created | data | Role | A entidade de papel criada. | |
| Role.Data.Updated | data | Role | A entidade de papel atualizada. | |
| Role.Deleted | data | null | / | |
| Role.Scopes.Updated | data | Scope[] | Os escopos atualizados atribuídos ao papel. | |
| Role.Scopes.Updated | roleId | string | ✅ | O ID do papel ao qual os escopos são atribuídos. (Disponível apenas quando o evento foi disparado pela criação de um papel com escopos pré-atribuídos.) |
Eventos de permissão (escopo)
| Evento | Campo | Tipo | Opcional | Observações |
|---|---|---|---|---|
| Scope.Created | data | Scope | A entidade de escopo criada. | |
| Scope.Data.Updated | data | Scope | A entidade de escopo atualizada. | |
| Scope.Deleted | data | null | / |
Eventos de organização
type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
| Evento | Campo | Tipo | Opcional | Observações |
|---|---|---|---|---|
| Organization.Created | data | Organization | A entidade de organização criada. | |
| Organization.Data.Updated | data | Organization | A entidade de organização atualizada. | |
| Organization.Deleted | data | null | / | |
| Organization.Membership.Updated | data | null | / | A alteração é descrita por arrays delta opcionais no topo. Veja Payload Organization.Membership.Updated abaixo. |
Payload Organization.Membership.Updated
Além dos campos comuns e dos campos de contexto de API que se aplicam à fonte do disparo (Contexto da Management API para rotas da Management API, Contexto da Experience API para provisionamento just-in-time), o evento Organization.Membership.Updated carrega um organizationId mais arrays delta opcionais no topo do payload (ao lado de event, createdAt, etc., não dentro de data, que é sempre null para este evento).
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| organizationId | string | A organização cuja associação foi alterada. | |
| addedUserIds | string[] | ✅ | IDs de usuários adicionados por este disparo. Omitido quando nenhum usuário foi adicionado, ou quando o disparo não afeta associação de usuários. |
| removedUserIds | string[] | ✅ | IDs de usuários removidos por este disparo. Omitido quando nenhum usuário foi removido. |
| addedApplicationIds | string[] | ✅ | IDs de aplicativos adicionados. Omitido quando nenhum aplicativo foi adicionado, ou quando o disparo não afeta associação de aplicativos. |
| removedApplicationIds | string[] | ✅ | IDs de aplicativos removidos. Omitido quando nenhum aplicativo foi removido. |
Os quatro arrays delta são opcionais e aditivos: eles não alteram o formato existente do payload para consumidores que não os esperam, e o campo legado data: null ainda é emitido sem alterações.
Disparos e quais campos delta eles podem emitir
| Disparo | Possíveis campos delta |
|---|---|
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 |
| Provisionamento just-in-time ao adicionar o usuário a uma nova organização | addedUserIds |
Deltas vazios são omitidos (ausente ≠ alteração vazia)
Arrays delta vazios são omitidos completamente do payload. Por exemplo, um PUT /organizations/:id/users que substitui o conjunto de membros pelo conjunto já existente não produz alteração real, e o payload se reduz a apenas { organizationId } com todos os quatro campos delta ausentes. O mesmo se aplica a uma re-adicionamento de um membro já existente e a uma reaceitação de convite por um usuário que já é membro.
Consumidores devem tratar um campo ausente como "sem alteração desse lado", não como "alteração vazia".
Limite por array (truncamento silencioso)
Cada array delta é limitado a 5000 entradas. Quando uma única chamada à Management API adiciona ou remove mais de 5000 usuários (ou aplicativos) em uma operação, o array delta correspondente é truncado silenciosamente para as primeiras 5000 entradas. Não há marcador no payload indicando que o limite foi atingido.
Se seu aplicativo realiza operações administrativas em massa que podem afetar plausivelmente mais de 5000 membros em uma chamada, trate um array com exatamente 5000 entradas como um sinal para reconciliar a associação autoritativa via Management API:
GET /organizations/:id/users: associação completa de usuários.GET /organizations/:id/applications: associação completa de aplicativos.
Isso segue o mesmo padrão do evento push do GitHub, que limita commits a 20 entradas e aponta consumidores para a compare API para a lista completa.
Pulando eventos sem alteração (no-op)
Para pular entregas sem alteração (eventos sem campos delta) do lado do consumidor, filtre pela presença dos arrays delta:
if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// alteração real de associação, trate aqui
}
?.length é falsy tanto para undefined quanto para [], então o mesmo predicado é robusto se o campo estiver ausente ou (em algum futuro hipotético) emitido como array vazio.
Exemplos de payloads
Adicionar um usuário (POST /organizations/:id/users):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_001"]
}
Substituir o conjunto de membros (PUT /organizations/:id/users):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_002"],
"removedUserIds": ["u_001"]
}
Remover um usuário (DELETE /organizations/:id/users/:userId):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_001"]
}
Adicionar um aplicativo (POST /organizations/:id/applications):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedApplicationIds": ["app_xyz"]
}
Re-adicionar um membro já existente, PUT sem alteração, ou reaceitação de convite de membro já existente (sem alteração real):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc"
}
Operação em massa que atinge o limite de 5000 (truncado silenciosamente):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_0001", "u_0002", "/* … exatamente 5000 entradas */"]
}
Ver um array com exatamente 5000 entradas deve acionar uma reconciliação via GET /organizations/:id/users (ou /applications).
Eventos de papel da organização
type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
| Evento | Campo | Tipo | Opcional | Observações |
|---|---|---|---|---|
| OrganizationRole.Created | data | OrganizationRole | A entidade de papel da organização criada. | |
| OrganizationRole.Data.Updated | data | OrganizationRole | A entidade de papel da organização atualizada. | |
| OrganizationRole.Deleted | data | null | / | |
| OrganizationRole.Scopes.Updated | data | null | / | |
| OrganizationRole.Scopes.Updated | organizationRoleId | string | ✅ | O ID do papel ao qual os escopos são atribuídos. (Disponível apenas quando o evento foi disparado pela criação de um papel com escopos pré-atribuídos.) |
Eventos de permissão (escopo) da organização
| Evento | Campo | Tipo | Opcional | Observações |
|---|---|---|---|---|
| OrganizationScope.Created | data | OrganizationScope | A entidade de escopo da organização criada. | |
| OrganizationScope.Data.Updated | data | OrganizationScope | A entidade de escopo da organização atualizada. | |
| OrganizationScope.Deleted | data | null | / |
Payloads de eventos de exceção
Eventos: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded.
Disparados em incidentes de segurança, por exemplo, uma conta bloqueada após tentativas consecutivas de verificação malsucedidas, ou concessões revogadas porque o limite de dispositivos autenticados simultâneos de um app foi excedido.
Todo evento de exceção carrega os campos comuns e um campo ip (mesmo formato dos eventos de mutação de dados). Os campos restantes dependem do evento.
Identifier.Lockout
Origina-se de um fluxo voltado ao usuário, então o corpo também carrega os campos de contexto da Experience API, além de:
enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| type | SignInIdentifier | O tipo de identificador do usuário, por exemplo, email, telefone ou nome de usuário. | |
| value | string | O valor do identificador do usuário que disparou o bloqueio. |
Message.RateLimited
Origina-se de um fluxo voltado ao usuário, então o corpo também carrega os campos de contexto da Experience API, além de:
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| action | string | A ação limitada por taxa, por exemplo, VerificationCodeSend. | |
| recipient | string | O endereço de email ou número de telefone que atingiu o limite de envio. |
Grant.LimitExceeded
Disparado quando uma autorização bem-sucedida faz com que um usuário ultrapasse o limite máximo de dispositivos autenticados simultâneos por app (maxAllowedGrants) e o Logto revoga suas concessões mais antigas para aquele app.
Este evento é emitido pelo endpoint de autorização OIDC em vez da Experience API, então não carrega interactionEvent ou sessionId. Junto com os campos comuns e ip, o corpo carrega:
| Campo | Tipo | Opcional | Observações |
|---|---|---|---|
| userId | string | O usuário cujas concessões foram revogadas. | |
| applicationId | string | O aplicativo cujo limite maxAllowedGrants foi excedido. | |
| application | ApplicationEntity | ✅ | A entidade de aplicativo. Omitido se o aplicativo não puder ser resolvido no momento da entrega. |
| maxAllowedGrants | number | O limite configurado no aplicativo quando o evento foi disparado. | |
| preRevocationActiveGrantCount | number | O número de concessões ativas que o usuário possuía para este aplicativo antes da revogação, incluindo a recém emitida. | |
| revokedGrantIds | string[] | Os IDs das concessões que foram realmente revogadas, das mais antigas para as mais recentes. |
Exemplo de payload:
{
"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": "Meu app",
"description": "Descrição do meu app"
},
"maxAllowedGrants": 2,
"preRevocationActiveGrantCount": 3,
"revokedGrantIds": ["grant_001"]
}
Notas sobre a entrega:
- Os registros de concessão revogados são destruídos como parte da revogação, então os IDs em
revokedGrantIdsnão são mais resolvidos pelos endpoints de listagem de concessões ou pelo Console — esses retornam apenas concessões ativas. Trate o payload como o registro, e persista os IDs do seu lado se precisar deles depois. - O evento só é disparado quando pelo menos uma concessão foi realmente revogada. Uma autorização que permanece dentro do limite não produz evento.
- Ele é disparado em toda autorização que excede o limite, então um usuário que faz login repetidamente de mais dispositivos do que o permitido produz um evento por exclusão.
- O envio é fire-and-forget: um endpoint lento ou com falha nunca bloqueia ou falha a autorização do usuário. Entregas com falha são registradas nos logs de auditoria como qualquer outro webhook.