Webhook Events 레퍼런스
본 페이지는 CROSS PAY API 가 발송하는 webhook 이벤트의 페이로드 스키마 사전입니다. 발화 시점·연동 흐름·머천트 측 처리 패턴은 Webhooks Overview 를 참고하세요.
본 페이지는 envelope 의 JSON 본문 스키마에 집중합니다.
- 서명 검증·replay 보호 → Verifying Webhooks
- 재시도·멱등성·dedup → Delivery & Retry
- 프로덕션 점검 → Production Checklist
HTTP 요청 헤더
| 헤더 | 형식 | 설명 |
|---|---|---|
Content-Type | application/json | 본문 인코딩 |
webhook-id | UUID | 이벤트 식별자. 멱등성 dedup 키 |
webhook-timestamp | Unix epoch seconds (string) | 발송 시점. replay 보호용 (권장 tolerance ±300초) |
webhook-signature | v1,<base64> (공백 구분 다중 가능) | HMAC-SHA256 서명. 서명 대상은 ${id}.${ts}.${body} |
서명 검증 알고리즘과 코드 예시는 Verifying Webhooks 를 참고하세요.
공통 envelope
CROSS PAY 는 Standard Webhooks v1 규격의 envelope 을 사용합니다.
{
"type": "<dot-delimited 이벤트 이름>",
"timestamp": "<ISO-8601 UTC, 도메인 이벤트 발생 시각>",
"data": {
"payment": { ... },
"refund": { ... }
}
}
| 필드 | 타입 | 설명 |
|---|---|---|
type | string | dot-delimited 이벤트 이름. 예: payment.completed |
timestamp | ISO-8601 | 도메인 이벤트가 발생한 시각 (paid_at / failed_at / ...). UTC Z |
data | object | 이벤트별 페이로드. payment 객체는 항상 존재 |
data.payment 공통 필드
모든 이벤트의 data.payment 객체는 다음 필드 집합에서 선택됩니다.
필드의 존재 여부는 이벤트 종류에 따라 달라집니다.
| 필드 | 타입 | 항상? | 설명 |
|---|---|---|---|
id | string | ✓ | 결제 식별자 (pay_...) |
status | string | ✓ | 결제 상태 (이벤트별 고정값, 아래 표 참조) |
currency | string | ✓ | ISO-4217 통화 코드. 예: KRW, USD |
amount | string | ✓ | 결제 금액. 정밀도 손실 방지를 위해 문자열로 직렬화 |
payer_uid | string | ✓ | 결제자 식별자 |
metadata | object | ✓ | 결제 생성 시 머천트가 넘긴 metadata. 빈 경우 {} |
receipt_id | string | PG 발급 영수증 ID (payment.completed 에만) | |
provider | string | 결제 처리 PG (payment.completed 에만). 예: binancepay | |
failure_reason | string | 실패 사유 enum (payment.failed 에만). 아래 enum 표 참조 | |
paid_at | ISO-8601 | 결제 확정 시각 (payment.completed) | |
cancelled_at | ISO-8601 | 취소 처리 시각 (payment.cancelled) | |
failed_at | ISO-8601 | 실패 처리 시각 (payment.failed) | |
expired_at | ISO-8601 | 만료 처리 시각 (payment.expired) |
status 이벤트별 매핑
| 이벤트 | 직전 상태 | status 값 |
|---|---|---|
payment.completed | PROCESSING | PAID |
payment.failed | PROCESSING | FAILED |
payment.expired | PENDING 또는 PROCESSING | EXPIRED |
payment.cancelled | PENDING | CANCELLED |
PROCESSING 경유 규칙payment.completed / payment.failed 는 결제가 반드시 PROCESSING 을 거쳐서
종결합니다 (POST /checkout/{id}/quotes 가 PG session 을 만든 시점부터). PG
webhook 이 도착하기 전까지는 머천트가 단독으로 cancel 호출이 불가합니다
— 409 INVALID_TRANSITION. 자세한 라이프사이클은 Webhooks Overview.
failure_reason enum
| 값 | 의미 |
|---|---|
UNKNOWN | PG 가 분류하지 않은 실패. fallback |
DECLINED | PG 가 결제를 거절 |
TX_REVERTED | 온체인 TX 가 mined 됐으나 컨트랙트 호출이 revert |
AMOUNT_MISMATCH | 온체인 이벤트 금액이 예상치와 불일치 |
TOKEN_MISMATCH | 온체인 이벤트 토큰이 예상치와 불일치 |
ONCHAIN_TIMEOUT | 만료 전에 온체인 컨펌을 관측하지 못함 |
이 집합은 추가 확장 가능합니다. 머천트는 인식하지 못하는 값을
UNKNOWN 으로 fallback 처리하세요.
결제 상태 머신
PENDING → PAID/FAILED직행은 허용되지 않습니다. PG·체인이 결제 결과를 보고하기 전에 반드시PROCESSING상태가 선행됩니다.PROCESSING → CANCELLED는 차단됩니다. PG 와의 race 를 피하기 위해 머천트 cancel API 는409 INVALID_TRANSITION으로 거부됩니다.- terminal 상태(
PAID/FAILED/EXPIRED/CANCELLED/*_REFUNDED) 에서 다른 종결 상태로 전이하지 않습니다. 같은 결제에 대해 두 종류의 종결 이벤트가 발화되는 경우는 없습니다.
payment.completed
PG·체인이 결제 확정을 보고하면 payment 상태가 PAID 로 전이되고
본 이벤트가 발화됩니다.
필드 구성
| 필드 | 필수 | 비고 |
|---|---|---|
id | ✓ | |
status | ✓ | 항상 PAID |
currency | ✓ | |
amount | ✓ | |
payer_uid | ✓ | |
receipt_id | ✓ | PG 발급 영수증 ID |
provider | ✓ | PG 식별자 (binancepay, payletter, ...) |
paid_at | ✓ | |
metadata | ✓ |
샘플
{
"type": "payment.completed",
"timestamp": "2026-04-21T12:00:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "PAID",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"receipt_id": "rcpt_xyz",
"provider": "binancepay",
"paid_at": "2026-04-21T12:00:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}
정산용 fee 정보는 본 페이로드에 포함되지 않습니다. 필요하면
GET /v1/payments/:id 로 보강 조회하세요.
payment.failed
PG·체인이 실패를 보고하면 payment 상태가 FAILED 로 전이되고
본 이벤트가 발화됩니다.
필드 구성
| 필드 | 필수 | 비고 |
|---|---|---|
id | ✓ | |
status | ✓ | 항상 FAILED |
currency | ✓ | |
amount | ✓ | |
payer_uid | ✓ | |
failure_reason | ✓ | 위 failure_reason enum 참조 |
failed_at | ✓ | |
metadata | ✓ |
샘플
{
"type": "payment.failed",
"timestamp": "2026-04-21T12:05:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "FAILED",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"failure_reason": "DECLINED",
"failed_at": "2026-04-21T12:05:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}
payment.expired
세션 TTL 이 만료되면 expiry 워커가 in-flight 결제(PENDING 또는 PROCESSING)
를 EXPIRED 로 전이시키고 본 이벤트가 발화됩니다.
필드 구성
| 필드 | 필수 | 비고 |
|---|---|---|
id | ✓ | |
status | ✓ | 항상 EXPIRED |
currency | ✓ | |
amount | ✓ | |
payer_uid | ✓ | |
expired_at | ✓ | |
metadata | ✓ |
샘플
{
"type": "payment.expired",
"timestamp": "2026-04-21T12:30:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "EXPIRED",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"expired_at": "2026-04-21T12:30:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}
payment.cancelled
머천트가 PENDING 상태의 결제를 POST /v1/payments/{id}/cancel 로 명시 취소하면
본 이벤트가 발화됩니다. PROCESSING 결제는 cancel 호출이 409 INVALID_TRANSITION
으로 거부되므로 본 이벤트가 발생하지 않습니다 — 해당 흐름은 payment.expired
또는 payment.failed 로 종결됩니다.
필드 구성
| 필드 | 필수 | 비고 |
|---|---|---|
id | ✓ | |
status | ✓ | 항상 CANCELLED |
currency | ✓ | |
amount | ✓ | |
payer_uid | ✓ | |
cancelled_at | ✓ | |
metadata | ✓ |
샘플
{
"type": "payment.cancelled",
"timestamp": "2026-04-21T12:10:00Z",
"data": {
"payment": {
"id": "pay_abc123",
"status": "CANCELLED",
"currency": "KRW",
"amount": "50000",
"payer_uid": "user_789",
"cancelled_at": "2026-04-21T12:10:00Z",
"metadata": { "order_id": "shop-order-789" }
}
}
}
payment.refunded (예약)
환불 완료 이벤트의 envelope 명세(data.refund 객체 포함)는 코드에
정의되어 있으나, CROSS PAY API 코드에는 현재 발화 경로가 연결되어
있지 않습니다. 머천트 endpoint 로 본 이벤트가 도착하지 않습니다.
환불 결과는 GET /v1/payments/:id 로 조회하세요.
발화 경로가 연결되면 본 페이지에 정식 스키마와 샘플이 추가됩니다.
알려진 제약
payment.created는 발화되지 않습니다. 결제 생성 결과는POST /v1/payments응답으로 동기 전달됩니다.metadata가 빈 경우{}로 직렬화됩니다.null이 아닙니다.- 모든 시간 필드는 ISO-8601 UTC (
Z접미사) 입니다. amount는 문자열입니다. 정밀도 손실 방지를 위해 number 가 아닌 string 으로 직렬화됩니다. 머천트는 decimal 라이브러리로 파싱 하세요.