SubXDocs

Webhooks

SubX sends events to registered URLs whenever subscription status changes. Payload integrity can be verified with HMAC-SHA256 signatures.

Event Types

EventDescription
INITIAL_PURCHASEInitial purchase completed
RENEWALSubscription auto-renewal succeeded
CANCELLATIONSubscription cancelled (expires on next renewal date)
UNCANCELLATIONCancellation reversed (subscription reactivated)
BILLING_ISSUEPayment failed (expired card, insufficient funds, etc.)
EXPIRATIONSubscription fully expired
PRODUCT_CHANGESubscription product changed (upgrade/downgrade)
REFUNDRefund processed

Payload Structure

All webhooks send JSON via POST in the following format:

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

Request Headers

HeaderDescription
Content-Typeapplication/json
X-SubX-SignatureHMAC-SHA256 signature
X-SubX-EventEvent type (e.g. INITIAL_PURCHASE)

Signature Verification

Verify payload integrity using the Signing Secret issued when registering the webhook. Always verify the signature before processing the event.

Node.js Example

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

중요: Important: Webhook handlers must return a 200 status code. Otherwise, SubX will retry the delivery.

Retry Policy

If webhook delivery fails (timeout or non-2xx response), it will automatically retry:

AttemptWait TimeCumulative Time
1st retryAfter 1 min+1 min
2nd retryAfter 10 min+11 min
3rd retryAfter 1 hour+1 hr 11 min

If all 3 retries fail, the event is recorded as failed. You can view failed events in the webhook logs on the dashboard and manually resend them.

Best Practices

  • 서명 검증 필수: Validate the X-SubX-Signature header on every webhook.
  • 멱등성 처리: The same event ID (evt_...) may be sent more than once. Use event IDs to prevent duplicate processing.
  • 빠른 응답: Return a 200 response within 5 seconds. Queue long-running tasks and process them asynchronously.
  • HTTPS 필수: Webhook URLs must use HTTPS.

Next Steps

  • Getting Started — SubX 설정 전체 과정을 확인합니다.
  • REST API — 전체 API 엔드포인트를 확인합니다.
Webhooks | SubX