Skip to Content
웹훅개요

웹훅 — 개요

웹훅은 권위 있는 신호입니다. 브라우저 콜백(onSuccess)과 대시보드 뷰는 편의 사항이며, 웹훅이 그라운드 트루스입니다.

전달 보장

  • At least once. 단일 이벤트는 가맹점 서버가 타임아웃 내에 2xx를 반환하지 않으면 최대 6회까지 전달될 수 있습니다. 핸들러를 멱등성 있게 만드세요 — X-Delivery로 중복을 제거하세요(페이로드에는 event_id 필드가 없으며, 안정적인 전달 UUID가 멱등성 키입니다).
  • HTTP 요청당 하나의 이벤트. 배치 없음.
  • 엔드포인트별 격리. 여러 엔드포인트가 등록되어 있다면 각각 자체 전달 + 재시도 트랙을 가집니다. 느린 가맹점 URL 하나가 다른 것을 굶주리게 할 수 없습니다 — 각 호스트는 자체 서킷 브레이커를 가집니다.
  • 서명됨. 모든 페이로드는 X-Signature 헤더(그리고 로테이션 후 24시간 윈도우 동안 X-Signature-Prev도)를 가집니다. 본문으로 무엇이든 하기 전에 검증하세요. 서명 검증을 참조하세요.

구독 가능한 이벤트 타입

이벤트발생 시점
payment.settled온체인 송금이 체인의 확인 수를 클리어함. 이것을 사용하여 주문을 결제 완료로 표시하세요.
payment.failed법정화폐 결제가 제공자에 의해 명시적으로 거부됨(현재: 실패를 시그널링하는 Stripe 웹훅). 암호화폐 타임아웃에는 발생하지 않음 — 그것은 대신 checkout.expired로 노출되고, 부족한 암호화폐 결제는 payment.underpaid로 노출됩니다.
payment.underpaid자금이 도착했지만 주문 총액에 부족함(일반적: 금액에서 스테이블코인 송금 수수료가 차감됨).
payment.overpaid자금이 주문 총액을 초과하여 도착함. 잉여는 기록되지만 자동 환불되지 않습니다.
order.created새 주문이 열림 — B2B API 호출 또는 체크아웃 세션 전환에 의해.
order.canceled주문이 취소됨. 페이로드의 data.reason이 수동 취소와 payment_timeout(워커가 스윕한 오래된 미결제 주문)을 구분합니다.
order.resolvedPARTIAL_PAID 주문이 PAID로 해결됨 — 가맹점이 부족분을 수용함.
order.reopened이전에 자동 취소된 주문(canceled_reason=payment_timeout)이 가맹점에 의해 재오픈됨.
checkout.created구매자가 주문의 체크아웃을 열었음.
checkout.completed구매자 측 흐름이 완료됨(온체인 정산을 의미하지 않음 — 그것은 payment.settled 사용).
checkout.expired구매자가 포기했고 세션 TTL이 끝남.
payment.refund.requested환불 기록이 생성됨 — 가맹점 시작 API 호출 또는 고객 제출 환불 요청 양식으로부터.
payment.refund.approved대기 중인 환불이 승인 워크플로우를 통과함.
payment.refund.rejected대기 중인 환불이 거부됨.
payment.refund.executed환불의 온체인 송금이 클리어되어 기록이 종료 상태 executed로 이동함.
refund_request.created환불 요청 토큰이 발급됨. data.sourceb2b / dashboard / renewal. 구독 선택 사항 — 주문별로 현재 활성화된 토큰을 추적하는 감사 파이프라인에 유용합니다.
refund_request.renewal_requested구매자가 토큰 만료 후 “새 링크 요청”을 클릭함. 구독을 강력히 권장합니다 — 갱신 위젯에 처리할 새 항목이 있음을 가맹점에 알리는 신호입니다.
refund_request.renewed갱신이 승인되어 새 토큰이 이전 토큰을 대체함. data.old_token / data.new_token이 감사 체인을 형성합니다.
refund_request.canceled가맹점이 대시보드에서 토큰을 CANCELED로 전환함(예: 갱신 요청 거부, 라이브 링크 종료). 멱등적 — 첫 전이만 발생합니다. data.reason은 선택적 가맹점 메모입니다.

대시보드는 이 목록을 GET /v1/webhooks/event-types에서 가져오므로 엔드포인트 생성/편집 양식은 항상 플랫폼이 실제로 발생시키는 것과 일치합니다. 저희가 출시하지 않는 이벤트를 구독하면 생성 시 명확한 오류로 거부됩니다.

테스트 이벤트는 구독할 수 없습니다. 대시보드의 엔드포인트별 Send Test 버튼은 해당 엔드포인트 하나에만 webhook.test.ping envelope를 동기적으로 POST하고(재시도 파이프라인 우회), 레거시 가맹점 레벨 “Send test event” 경로는 필터와 관계없이 모든 활성 엔드포인트로 webhook.test envelope를 팬아웃합니다. 위 카탈로그에는 둘 다 나타나지 않습니다 — 등록된 엔드포인트가 있다는 이유로 받는 것이며, 구독에 의한 것이 아닙니다.

처리하는 이벤트만 구독하세요. 각 엔드포인트는 자체 이벤트 필터를 가집니다. 와일드카드 "*"는 “미래에 추가될 것을 포함한 모든 이벤트”를 의미합니다. 더 적은 이벤트를 구독하면 핸들러가 더 깔끔해지고 동시에 오류 시 재시도해야 하는 표면적이 줄어듭니다.

페이로드 + 헤더

HTTP 본문은 이벤트별 데이터 객체 자체입니다. Stripe 스타일의 외부 envelope 없음 — 이벤트 타입, 전달 ID, 발신 타임스탬프와 같은 필드는 헤더에 있습니다. payment.settled의 경우 본문은 다음과 같습니다.

{ "receipt_id": "rcp_…", "order_id": "ord_…", "payment_intent_id": "pin_…", "checkout_session_id": "cst_…", "merchant_id": "mer_…", "customer_id": "cus_…", "total": "49.00", "currency": "USD", "payment_method": "crypto", "token": "USDC", "network": "polygon", "tx_hash": "0x…", "deposit_address": "0x…", "treasury_address": "0x…", "amount_received": "49.00", "confirmations": 5, "metadata": { /* 이벤트별 */ } }

다른 이벤트는 자체 필드 세트를 가집니다 — 이벤트별 문서가 출시되기 전까지 정규 형식은 payment-service/internal/domain/events.go의 publisher 구조체를 참조하세요. 필드 이름은 안정적입니다(소문자 스네이크 케이스); 온체인 tx 해시는 항상 tx_hash(transaction_hash가 아님)입니다.

인바운드 요청의 헤더

Content-Type: application/json X-Event: payment.settled X-Delivery: 7c9e6679-7425-40de-944b-e07fc1f90ae7 Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7 X-Timestamp: 1729536000 X-Signature: sha256=9a8b7c… X-Signature-Prev: sha256=fa31b2… (로테이션 grace 윈도우 동안에만)
헤더내용
X-Event이벤트 타입(예: payment.settled). JSON 파싱을 건너뛰려면 프록시 레이어에서 이것으로 라우팅하세요.
X-Delivery전달 행을 식별하는 UUID. 동일한 (event, endpoint) 쌍의 모든 재시도에서 안정적 — 멱등성 키로 사용하세요.
Idempotency-KeyX-Delivery와 미러링(동일한 값). 모든 전달에 설정됨 — Stripe / GitHub 관례를 따릅니다.
X-Timestamp시도가 전송된 유닉스 초. 페이로드에 서명되므로 캡처된 (body, X-Signature) 쌍이 무한정 재생될 수 없습니다 — 타임스탬프가 허용 윈도우 밖인 전달을 거부하세요.
X-SignatureHMAC-SHA256(secret, X-Timestamp + "." + raw_body)sha256=<hex>. 서명 검증 참조.
X-Signature-Prev이전 시크릿으로 동일한 알고리즘. 로테이션 후 24시간 윈도우에만 존재 — 어느 쪽 키를 실행 중인 검증자도 컷오버 동안 전달을 계속 받을 수 있게 합니다. 윈도우가 닫힌 후 헤더는 더 이상 전송되지 않습니다.

재시도 스케줄

엔드포인트가 타임아웃 내에 2xx를 반환하지 않으면 다음 스케줄로 재시도합니다 (타임스탬프는 첫 시도 기준).

시도지연누적
10s0s
2+1 min1m
3+5 min6m
4+15 min21m
5+1 hour1h 21m
6+6 hours7h 21m

6번째 시도가 실패하면 전달은 데드 레터로 이동되고 가맹점 계정 이메일에 알림이 전송됩니다. 데드 레터링된 이벤트는 대시보드의 Developers → Webhooks → Delivery history 패널에서 또는 POST /v1/webhooks/deliveries/:id/replay로 직접 재생할 수 있습니다. 각 재생은 자체 X-Delivery로 새 전달 행을 생성합니다 — 감사 체인은 parent_delivery_id를 통해 원본과 연결되므로 재생의 재시도가 소스 이벤트를 가리지 않습니다.

엔드포인트 등록

가맹점 대시보드 에서:

  1. Developers → Webhooks+ Add endpoint
  2. URL 붙여넣기 — https://…만(일반 HTTP는 거부됨; 생성 양식도 localhost, 사설 IP 범위, userinfo가 있는 URL을 차단함)
  3. 구독할 이벤트 선택(또는 * 모두)
  4. 환경 선택 — test 또는 live(각각 자체 시크릿을 가지며 절대 교차되지 않음)
  5. 저장 → 대시보드가 서명 시크릿(whsec_…)을 한 번 표시합니다. 서버 측에 저장하세요 — 다음 두 기능에 필요합니다.

가맹점당 환경당 최대 10개의 엔드포인트(예: 프로덕션 이행용 하나, 스테이징 미러링용 하나, Slack 알림용 하나)를 등록할 수 있습니다. 각각 자체 재시도 상태, 시크릿, 호스트별 서킷 브레이커를 유지합니다.

각 엔드포인트의 라이프사이클 액션

각 엔드포인트 카드의 ⋮ 메뉴는 다음을 노출합니다.

  • Edit — URL, 설명 또는 구독 목록 변경. 새 URL은 생성과 동일한 https:///SSRF 규칙으로 재검증됩니다.
  • Send Test — 현재 시크릿으로 서명된 webhook.test.ping envelope를 동기적으로 POST합니다. 대시보드는 HTTP 상태, 지연, 응답의 512바이트 스니펫을 표시합니다. RMQ 파이프라인을 우회하여 답변이 즉시 반환됩니다.
  • Rotate Secret — 새 시크릿을 생성합니다. 이전 시크릿은 24시간 동안 유효합니다(전달은 X-SignatureX-Signature-Prev를 모두 포함하여 윈도우 동안 어느 쪽 키를 실행 중인 검증자도 재배포 중에 이벤트를 계속 받을 수 있게 합니다).
  • Reveal Secret — 기존 시크릿을 다시 표시합니다. 새 2FA 검증으로 게이팅되고 감사 로그에 기록됨. 사본을 분실했고 Rotate가 허용되지 않는 경우에만 사용하세요.
  • Enable / Disable — 전달 이력을 잃지 않고 is_active를 토글합니다. 비활성화된 엔드포인트는 대시보드에 남아 있지만 새 전달을 받지 않습니다.
  • Delete — 영구적입니다. 나중에 재활성화할 수 있는 경우 Disable을 사용하세요.

핸들러를 위한 팁

  1. 빠르게 2xx 반환. 무거운 작업을 수행하기 전에 200 OK로 응답하세요 — 이행을 백그라운드 큐로 보내세요. 시도당 타임아웃은 10초입니다. 더 오래 응답을 보류하면 재시도가 트리거됩니다. 타임아웃은 플랫폼 측이며 가맹점이 구성할 수 없습니다 — 핸들러가 진정으로 더 많은 시간이 필요한 경우 지원팀에 문의하세요.
  2. X-Delivery로 중복 제거(또는 Idempotency-Key — 동일한 값). 2xx를 반환해도 업스트림 프록시가 연결을 끊고 재시도를 트리거할 수 있습니다. 전달 ID는 동일한 전달 행의 모든 재시도에서 안정적이므로 올바른 키입니다.
  3. 알 수 없는 이벤트 타입을 허용. 새 이벤트가 나타날 수 있습니다. 4xx 대신 200을 반환하고 no-op 하세요. 그렇지 않으면 재시도 큐가 채워집니다.
  4. 비즈니스 로직 옆에 X-Delivery 로깅. 무언가 잘못되면 그것이 저희 측과 가맹점 측 사이의 조인 키입니다.

다음 단계

  • 서명 검증 — 정확한 알고리즘 + 리플레이 보호 패턴.
  • 개념 → 세션 — 각 이벤트 발생 시 세션이 어떤 상태에 있는지.