SubXDocs

웹훅

SubX는 구독 상태가 변경될 때마다 등록된 URL로 이벤트를 전송합니다. HMAC-SHA256 서명으로 페이로드 무결성을 검증할 수 있습니다.

이벤트 타입

이벤트설명
INITIAL_PURCHASE최초 구매 완료
RENEWAL구독 자동 갱신 성공
CANCELLATION구독 취소 (다음 갱신일에 만료 예정)
UNCANCELLATION취소 철회 (구독 재활성화)
BILLING_ISSUE결제 실패 (카드 만료, 잔액 부족 등)
EXPIRATION구독 완전 만료
PRODUCT_CHANGE구독 상품 변경 (업그레이드/다운그레이드)
REFUND환불 처리 완료

페이로드 구조

모든 웹훅은 다음 형식의 JSON을 POST로 전송합니다:

webhook-payload.json
{
  "id": "evt_abc123",
  "type": "INITIAL_PURCHASE",
  "timestamp": "2026-02-18T09:30:00Z",
  "projectId": "proj_abc123",
  "data": {
    "customerId": "cust_xyz789",
    "appUserId": "user_12345",
    "productId": "prod_monthly",
    "productIdentifier": "com.example.pro.monthly",
    "entitlements": ["pro"],
    "store": "APP_STORE",
    "currency": "KRW",
    "price": 9900,
    "transactionId": "txn_abc123",
    "purchaseDate": "2026-02-18T09:30:00Z",
    "expirationDate": "2026-03-18T09:30:00Z"
  }
}

전송 헤더

헤더설명
Content-Typeapplication/json
X-SubX-SignatureHMAC-SHA256 서명값
X-SubX-Event이벤트 타입 (예: INITIAL_PURCHASE)

서명 검증

웹훅 등록 시 발급되는 Signing Secret을 사용하여 페이로드의 무결성을 검증합니다. 반드시 서명을 검증한 후 이벤트를 처리하세요.

Node.js 예시

import crypto from "crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express 핸들러 예시
app.post("/webhooks/subx", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-subx-signature"] as string;
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = JSON.parse(payload);

  switch (event.type) {
    case "INITIAL_PURCHASE":
      // 최초 구매 처리
      handleInitialPurchase(event.data);
      break;
    case "RENEWAL":
      // 갱신 처리
      handleRenewal(event.data);
      break;
    case "CANCELLATION":
      // 취소 처리
      handleCancellation(event.data);
      break;
    // ... 기타 이벤트
  }

  // 200 응답 (필수)
  res.status(200).json({ received: true });
});

중요: 중요: 웹훅 핸들러는 반드시 200 상태 코드를 반환해야 합니다. 그렇지 않으면 SubX가 재시도를 수행합니다.

재시도 정책

웹훅 전송에 실패하면(타임아웃 또는 비-2xx 응답) 자동으로 재시도합니다:

시도대기 시간누적 시간
1차 재시도1분 후+1분
2차 재시도10분 후+11분
3차 재시도1시간 후+1시간 11분

3회 재시도 후에도 실패하면 해당 이벤트는 실패 상태로 기록됩니다. 대시보드의 웹훅 로그에서 실패한 이벤트를 확인하고 수동으로 재전송할 수 있습니다.

모범 사례

  • 서명 검증 필수: 모든 웹훅에서 X-SubX-Signature 헤더를 검증하세요.
  • 멱등성 처리: 동일한 이벤트 ID(evt_...)가 중복 전송될 수 있습니다. 이벤트 ID를 기준으로 중복 처리를 방지하세요.
  • 빠른 응답: 웹훅 핸들러는 5초 내에 200 응답을 반환하세요. 오래 걸리는 작업은 큐에 넣고 비동기로 처리하세요.
  • HTTPS 필수: 웹훅 URL은 반드시 HTTPS를 사용해야 합니다.

다음 단계

  • 시작하기 — SubX 설정 전체 과정을 확인합니다.
  • REST API — 전체 API 엔드포인트를 확인합니다.
웹훅 | SubX