본문으로 건너뛰기

Webhooks 요청 (Webhooks request)

Webhook 이벤트가 발생하면, Logto는 해당 이벤트에 구독된 모든 엔드포인트에 POST 요청을 보냅니다. 전체 이벤트 카탈로그는 Webhooks 이벤트에서 확인할 수 있습니다. 이 페이지에서는 Logto가 전달하는 요청의 형태를 문서화합니다.

요청 헤더​

KeyCustomizableNotes
user-agent✅기본값은 Logto (https://logto.io/)입니다.
content-type✅기본값은 application/json입니다.
logto-signature-sha-256요청 본문의 서명입니다. Webhooks 보안 설정 참고.

커스터마이즈 가능한 헤더는 보안 webhook 설정을 통해 오버라이드할 수 있습니다.

요청 본문 개요​

본문은 JSON 객체입니다. 정확한 형태는 이벤트가 속한 패밀리에 따라 다릅니다:

FamilyEventsWhen it fires
사용자 흐름PostRegister, PostSignIn, PostResetPassword사용자가 Experience API에서 회원가입, 로그인, 비밀번호 재설정 흐름을 완료할 때 발생합니다.
데이터 변경User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*Management API 호출 또는 Experience API의 사용자 흐름에서 데이터 모델이 변경될 때 발생합니다.
예외Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded보안 사고 발생 시, 예를 들어 연속된 인증 실패로 계정이 잠긴 경우 등.

모든 패밀리는 공통 필드를 공유합니다. 각 패밀리는 자체 요청 컨텍스트 필드와 이벤트별 페이로드를 추가로 포함합니다.

공통 필드​

패밀리와 관계없이 모든 전달에 포함됩니다:

FieldTypeOptionalNotes
hookIdstringLogto의 webhook 구성 식별자입니다.
deliveryIdstring이 전달의 식별자입니다. webhook 및 이벤트별로 고유하며, 동일 요청의 자동 재시도 동안 유지됩니다. 전달 deduplication 참고.
eventstring이 전달을 트리거한 이벤트입니다.
createdAtstringISO 8601 형식의 페이로드 생성 시간입니다.
userAgentstring✅트리거 요청의 user-agent입니다.

각 패밀리에는 트리거 요청의 IP 주소도 포함됩니다. 사용자 흐름 이벤트에서는 userIp, 데이터 변경 및 예외 이벤트에서는 ip 필드명으로 제공됩니다. 의미는 동일하며, 과거 호환성을 위해 이름만 다릅니다.

전달 deduplication​

전달은 엔드포인트에 한 번 이상 도달할 수 있습니다. Logto는 응답 상태가 500 이상인 요청을 재시도하므로, 엔드포인트가 요청을 처리했지만 5xx로 응답한 경우(예: 느린 핸들러 앞의 게이트웨이가 504 반환) 다시 도착할 수 있습니다. 동일 전달의 모든 시도는 같은 deliveryId를 가지며, 새로운 이벤트이거나 다른 webhook에 전송된 동일 이벤트는 새로운 ID를 받습니다. 이미 처리한 페이로드의 deliveryId를 기록하고, 이미 아는 ID의 페이로드는 건너뛰세요.

deliveryId는 전달을 기록하는 감사 로그 항목의 ID이기도 하므로, 서비스에서 보고한 값을 Console의 webhook 최근 요청에서 열거나 GET /api/logs/:id로 가져올 수 있습니다.

테스트 페이로드도 deliveryId를 포함하므로, 동일 핸들러로 처리할 수 있습니다. 단, 테스트 요청은 로그에 기록되지 않습니다.

사용자 흐름 이벤트 페이로드​

이벤트: PostRegister, PostSignIn, PostResetPassword.

사용자가 Experience API에서 회원가입, 로그인, 비밀번호 재설정 흐름을 완료할 때 발생합니다. 공통 필드 외에, 본문에는 다음이 포함됩니다:

FieldTypeOptionalNotes
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'사용자 흐름 이벤트 타입입니다. 각각 PostSignIn / PostRegister / PostResetPassword에 매핑됩니다. 필드명은 과거 "interaction" 명명법을 유지합니다.
sessionIdstring✅해당 이벤트의 세션 ID (Interaction ID 아님), 해당되는 경우에만 포함됩니다.
userIpstring✅트리거 요청의 IP 주소입니다.
userIdstring✅해당 이벤트와 연관된 사용자 ID, 해당되는 경우에만 포함됩니다.
userUserEntity✅해당 이벤트와 연관된 사용자 엔티티, 해당되는 경우에만 포함됩니다.
applicationIdstring✅해당 이벤트와 연관된 애플리케이션 ID, 해당되는 경우에만 포함됩니다.
applicationApplicationEntity✅해당 이벤트와 연관된 애플리케이션 엔티티, 해당되는 경우에만 포함됩니다.

엔티티 형태​

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

전체 필드 참조는 사용자 및 애플리케이션 문서를 참고하세요.

데이터 변경 이벤트 페이로드​

이벤트: User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* 아래의 모든 이벤트. 전체 카탈로그는 Webhooks 이벤트 → 데이터 변경 webhook 이벤트에서 확인하세요.

본문에는 항상 다음이 포함됩니다:

  • 공통 필드
  • 트리거 요청의 IP 주소를 담는 ip 필드 (선택적, 알려진 경우에만)
  • 변경이 어떻게 트리거되었는지 설명하는 API 컨텍스트. 트리거 소스에 따라 두 가지 변형 중 하나입니다:
  • 이벤트별 페이로드: data에 영향을 받은 엔티티, 일부 이벤트는 추가 최상위 필드 포함. 이벤트별 데이터 페이로드 참고.

Experience API 컨텍스트 필드​

Experience API의 사용자 대상 흐름에서 트리거된 경우에 포함됩니다. 예: 회원가입 중 User.Created, 프로필 업데이트 중 User.Data.Updated 등.

FieldTypeOptionalNotes
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'✅변경을 발생시킨 사용자 흐름 이벤트 타입. 필드명은 과거 "interaction" 명명법을 유지합니다.
sessionIdstring✅해당 이벤트의 세션 ID (Interaction ID 아님), 해당되는 경우에만 포함됩니다.
applicationIdstring✅애플리케이션 ID, 해당되는 경우에만 포함됩니다.
applicationApplicationEntity✅애플리케이션 엔티티, 해당되는 경우에만 포함됩니다.

Management API 컨텍스트 필드​

Management API 호출로 트리거된 경우에 포함됩니다.

FieldTypeOptionalNotes
pathstring✅이 webhook을 트리거한 API 호출의 경로입니다.
methodstring✅API 호출의 HTTP 메서드입니다.
statusnumber✅API 호출의 응답 상태 코드입니다.
paramsobject✅API 호출의 koa path params입니다.
matchedRoutestring✅koa에서 매칭된 라우트입니다. Logto는 이 필드를 사용해 활성화된 webhook 이벤트 필터와 매칭합니다.

이벤트별 데이터 페이로드​

모든 데이터 변경 이벤트는 영향을 받은 엔티티를 담는 최상위 data 필드를 포함하며, 변경이 단일 엔티티로 요약될 수 없는 경우(삭제 및 멤버십 이벤트 등)에는 null입니다. 일부 이벤트는 data 외에 이벤트별 최상위 필드를 추가로 포함합니다. Organization.Membership.Updated가 그 예로, 아래에 문서화되어 있습니다.

사용자 이벤트​

EventFieldTypeOptionalNotes
User.CreateddataUserEntity생성된 사용자 엔티티.
User.Data.UpdateddataUserEntity업데이트된 사용자 엔티티.
User.Deleteddatanull/

역할 이벤트​

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;
};
EventFieldTypeOptionalNotes
Role.CreateddataRole생성된 역할 엔티티.
Role.Data.UpdateddataRole업데이트된 역할 엔티티.
Role.Deleteddatanull/
Role.Scopes.UpdateddataScope[]역할에 할당된 업데이트된 스코프.
Role.Scopes.UpdatedroleIdstring✅스코프가 할당된 역할 ID. (사전 할당된 스코프로 역할을 생성할 때만 제공)

권한(스코프) 이벤트​

EventFieldTypeOptionalNotes
Scope.CreateddataScope생성된 스코프 엔티티.
Scope.Data.UpdateddataScope업데이트된 스코프 엔티티.
Scope.Deleteddatanull/

조직 이벤트​

type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
EventFieldTypeOptionalNotes
Organization.CreateddataOrganization생성된 조직 엔티티.
Organization.Data.UpdateddataOrganization업데이트된 조직 엔티티.
Organization.Deleteddatanull/
Organization.Membership.Updateddatanull/변경 사항은 선택적 최상위 델타 배열로 설명됩니다. Organization.Membership.Updated 페이로드 참고.
Organization.Membership.Updated 페이로드​

공통 필드 및 트리거 소스에 해당하는 API 컨텍스트 필드(Management API 컨텍스트 또는 Experience API 컨텍스트) 외에, Organization.Membership.Updated 이벤트는 organizationId와 선택적 델타 배열을 페이로드 최상위에 포함합니다(event, createdAt 등과 같은 레벨, 항상 data 내부가 아님. 이 이벤트의 data는 항상 null).

FieldTypeOptionalNotes
organizationIdstring멤버십이 변경된 조직의 ID.
addedUserIdsstring[]✅이번 트리거로 새로 추가된 사용자 ID. 추가된 사용자가 없거나, 트리거가 사용자 멤버십에 영향을 주지 않으면 생략.
removedUserIdsstring[]✅이번 트리거로 제거된 사용자 ID. 제거된 사용자가 없으면 생략.
addedApplicationIdsstring[]✅새로 추가된 애플리케이션 ID. 추가된 애플리케이션이 없거나, 트리거가 애플리케이션 멤버십에 영향을 주지 않으면 생략.
removedApplicationIdsstring[]✅제거된 애플리케이션 ID. 제거된 애플리케이션이 없으면 생략.

네 개의 델타 배열은 선택적이며 추가적입니다. 즉, 이 배열이 없는 경우에도 기존 페이로드 형태는 변하지 않으며, 레거시 data: null 필드는 그대로 유지됩니다.

트리거별 델타 필드 예시​
TriggerPossible delta fields
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
Just-in-time provisioning when adding the user to a new organizationaddedUserIds
빈 델타는 생략됨 (없음 ≠ 빈 변경)​

빈 델타 배열은 페이로드에서 완전히 생략됩니다. 예를 들어, 기존 멤버십과 동일한 집합으로 교체하는 PUT /organizations/:id/users는 실제 변경이 없으므로, 페이로드는 { organizationId }만 남고 네 개의 델타 필드는 모두 생략됩니다. 기존 멤버 재추가, 이미 멤버인 사용자의 초대 재수락 등도 마찬가지입니다.

컨슈머는 필드가 없으면 "해당 측면에 변경 없음"으로 간주해야 하며, "빈 변경"으로 해석하면 안 됩니다.

배열별 최대값 (조용한 잘림)​

각 델타 배열은 최대 5000개 항목으로 제한됩니다. 한 번의 Management API 호출로 5000명(또는 애플리케이션) 이상을 추가/제거하면, 해당 델타 배열은 처음 5000개 항목까지만 조용히 잘립니다. 페이로드 내에 제한이 발동되었음을 알리는 마커는 없습니다.

관리자 대량 작업이 한 번에 5000명 이상에 영향을 줄 수 있다면, 배열이 정확히 5000개일 때 Management API로 멤버십을 재조정하세요:

  • GET /organizations/:id/users: 전체 사용자 멤버십.
  • GET /organizations/:id/applications: 전체 애플리케이션 멤버십.

이 방식은 GitHub의 push 이벤트가 commits를 20개로 제한하고, 전체 목록은 compare API로 안내하는 패턴과 동일합니다.

no-op 이벤트 건너뛰기​

컨슈머 측에서 no-op 전달(델타 필드가 없는 이벤트)을 건너뛰려면, 델타 배열 존재 여부로 필터링하세요:

if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// 실제 멤버십 변경, 처리 필요
}

?.length는 undefined와 [] 모두에 대해 falsy이므로, 필드가 없거나(생략) 향후 빈 배열로 발행되어도 동일하게 동작합니다.

페이로드 예시​

사용자 추가 (POST /organizations/:id/users):

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

사용자 멤버십 집합 교체 (PUT /organizations/:id/users):

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

사용자 제거 (DELETE /organizations/:id/users/:userId):

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

애플리케이션 추가 (POST /organizations/:id/applications):

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

기존 멤버 재추가, no-op PUT, 이미 멤버인 초대 재수락(실제 변경 없음):

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

5000개 제한에 걸린 대량 작업(조용히 잘림):

{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_0001", "u_0002", "/* … 총 5000개 항목 */"]
}

정확히 5000개 항목이 있는 배열을 보면 GET /organizations/:id/users(또는 /applications)로 재조정해야 함을 의미합니다.

조직 역할 이벤트​

type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
EventFieldTypeOptionalNotes
OrganizationRole.CreateddataOrganizationRole생성된 조직 역할 엔티티.
OrganizationRole.Data.UpdateddataOrganizationRole업데이트된 조직 역할 엔티티.
OrganizationRole.Deleteddatanull/
OrganizationRole.Scopes.Updateddatanull/
OrganizationRole.Scopes.UpdatedorganizationRoleIdstring✅스코프가 할당된 역할 ID. (사전 할당된 스코프로 역할을 생성할 때만 제공)

조직 권한(스코프) 이벤트​

EventFieldTypeOptionalNotes
OrganizationScope.CreateddataOrganizationScope생성된 조직 스코프 엔티티.
OrganizationScope.Data.UpdateddataOrganizationScope업데이트된 조직 스코프 엔티티.
OrganizationScope.Deleteddatanull/

예외 이벤트 페이로드​

이벤트: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded.

보안 사고 발생 시, 예를 들어 연속된 인증 실패로 계정이 잠기거나, 앱의 동시 인증 디바이스 제한을 초과해 grant가 퇴출된 경우에 발생합니다.

모든 예외 이벤트는 공통 필드와 ip 필드(데이터 변경 이벤트와 동일한 형태)를 포함합니다. 나머지 필드는 이벤트에 따라 다릅니다.

Identifier.Lockout​

사용자 대상 흐름에서 발생하므로, 본문에는 Experience API 컨텍스트 필드도 포함되며, 추가로 다음이 포함됩니다:

enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
FieldTypeOptionalNotes
typeSignInIdentifier사용자의 식별자 타입(예: email, phone, username).
valuestring잠금 트리거가 된 사용자의 식별자 값.

Message.RateLimited​

사용자 대상 흐름에서 발생하므로, 본문에는 Experience API 컨텍스트 필드도 포함되며, 추가로 다음이 포함됩니다:

FieldTypeOptionalNotes
actionstring제한된 액션(예: VerificationCodeSend).
recipientstring발송 속도 제한에 걸린 이메일 주소 또는 전화번호.

Grant.LimitExceeded​

성공적인 인가(Authorization)가 사용자를 앱의 최대 동시 인증 디바이스 제한(maxAllowedGrants)을 초과하게 만들고, Logto가 해당 앱의 가장 오래된 grant를 회수할 때 발생합니다.

이 이벤트는 Experience API가 아닌 OIDC 인가 엔드포인트에서 발생하므로, interactionEvent나 sessionId를 포함하지 않습니다. 공통 필드와 ip 외에, 본문에는 다음이 포함됩니다:

FieldTypeOptionalNotes
userIdstringgrant가 회수된 사용자 ID.
applicationIdstringmaxAllowedGrants 제한을 초과한 애플리케이션 ID.
applicationApplicationEntity✅애플리케이션 엔티티. 전달 시점에 애플리케이션을 확인할 수 없는 경우 생략.
maxAllowedGrantsnumber이벤트 발생 시 애플리케이션에 설정된 제한값.
preRevocationActiveGrantCountnumber회수 전 사용자가 해당 애플리케이션에 보유한 활성 grant 수(방금 발급된 것 포함).
revokedGrantIdsstring[]실제로 회수된 grant의 ID(오래된 것부터).

예시 페이로드:

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

전달 참고사항:

  • 회수된 grant 레코드는 회수 과정에서 삭제되므로, revokedGrantIds의 ID는 grant 목록 엔드포인트나 Console에서 더 이상 조회되지 않습니다(이들은 활성 grant만 반환). 필요한 경우 페이로드를 기록해두세요.
  • 이벤트는 실제로 grant가 회수된 경우에만 발생합니다. 제한 내에 머무는 인가는 이벤트를 발생시키지 않습니다.
  • 제한을 초과하는 인가가 있을 때마다 발생하므로, 사용자가 허용된 디바이스 수보다 더 많은 기기에서 반복적으로 로그인하면 퇴출마다 이벤트가 발생합니다.
  • 전달은 fire-and-forget 방식입니다. 느리거나 실패하는 엔드포인트가 사용자 인가를 차단하거나 실패시키지 않습니다. 실패한 전달도 다른 webhook과 마찬가지로 감사 로그에 기록됩니다.