2026-08-08

웹훅 서명 검증(HMAC-SHA256) 구현하는 법

웹훅 서명 검증(HMAC-SHA256) 구현하는 법

결제 웹훅은 외부에서 우리 서버로 들어오는 요청입니다. 누구나 같은 URL로 가짜 요청을 보낼 수 있기 때문에, 발신자가 진짜인지 반드시 검증해야 합니다.

왜 서명 검증이 필요한가

웹훅 URL이 공개되면 악의적인 요청자가 invoice.payment_succeeded 같은 이벤트를 위조해서 보낼 수 있습니다. 서명 검증 없이 이 요청을 그대로 믿고 처리하면, 결제하지 않은 사용자에게 서비스를 제공하는 사고로 이어질 수 있습니다.

HMAC-SHA256 방식

슈퍼빌링은 웹훅 등록 시 발급되는 시크릿으로 요청 본문을 서명해 X-SuperBilling-Signature 헤더에 담아 보냅니다.

import { verifyWebhookSignature } from "@superbilling/sdk-node";

export async function POST(req: Request) {
  const rawBody = await req.text(); // 반드시 raw body 문자열을 그대로 사용
  const signature = req.headers.get("x-superbilling-signature") ?? "";

  if (!verifyWebhookSignature(rawBody, signature, process.env.SUPERBILLING_WEBHOOK_SECRET!)) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = JSON.parse(rawBody);
  // event.type: "subscription.created" | "invoice.payment_succeeded" |
  //             "invoice.payment_failed" | "subscription.canceled"
  return new Response("ok", { status: 200 });
}

자주 하는 실수

  • raw body 대신 파싱된 JSON을 서명 대상으로 쓰는 것: 키 순서나 공백이 바뀌면 서명이 항상 실패합니다. 반드시 요청 본문 문자열 그대로를 사용해야 합니다.
  • 재시도를 고려하지 않는 것: 전송 실패 시 최대 3회(1초/3초 간격)까지 재시도되므로, 핸들러는 같은 이벤트를 여러 번 받아도 안전하게 처리(idempotent)해야 합니다.

자세한 이벤트 목록과 등록 방법은 Quickstart 문서에서 확인할 수 있습니다.