보안 검증

수신한 Webhook 요청이 실제로 apiacc에서 전송되었는지 서명을 검증하는 방법을 설명합니다.

모든 Webhook 요청에는 Apiacc-Timestamp, Apiacc-Event-Id, Apiacc-Signature 헤더가 포함됩니다. Apiacc-Signature 값은 v1=서명 형식이며, Webhook 등록 시 발급된 webhook_secret으로 요청 본문을 HMAC-SHA256으로 서명한 값입니다.

검증 절차

{timestamp}.{event_id}.{raw_body} 형태의 바이트열을 webhook_secret으로 HMAC-SHA256 서명한 뒤, 헤더의 Apiacc-Signature값과 상수 시간 비교로 일치 여부를 확인합니다.

Node.js
import crypto from "node:crypto"

function headerValue(value: string | string[] | undefined) {
  return Array.isArray(value) ? value[0] : value ?? ""
}

function verifySignature(rawBody: Buffer, headers: Record<string, string | string[] | undefined>, secret: string) {
  const timestamp = headerValue(headers["apiacc-timestamp"])
  const eventId = headerValue(headers["apiacc-event-id"])
  const signature = headerValue(headers["apiacc-signature"])

  if (!timestamp || !eventId || !signature.startsWith("v1=")) {
    return false
  }

  const signedPayload = Buffer.concat([Buffer.from(timestamp + "." + eventId + "."), rawBody])
  const expected = Buffer.from(
    "v1=" + crypto.createHmac("sha256", secret).update(signedPayload).digest("hex"),
  )
  const actual = Buffer.from(signature)

  const age = Math.abs(Date.now() / 1000 - Number(timestamp))
  if (age > 300 || expected.length !== actual.length) {
    return false
  }

  return crypto.timingSafeEqual(expected, actual)
}

Python 예시

Python
import hashlib
import hmac
import time

def verify_signature(raw_body: bytes, timestamp: str, event_id: str, signature: str, secret: str) -> bool:
    if not timestamp or not event_id or not signature.startswith("v1="):
        return False

    signed_payload = timestamp.encode() + b"." + event_id.encode() + b"." + raw_body
    expected = "v1=" + hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()

    if abs(time.time() - int(timestamp)) > 300:
        return False

    return hmac.compare_digest(expected, signature)

raw body를 사용하세요

서명 검증에는 JSON.parse로 다시 직렬화한 본문이 아니라, 요청에서 받은 원본(raw) 바이트를 그대로 사용해야 합니다. 프레임워크의 body parser가 자동으로 파싱하는 경우 raw body 미들웨어를 별도로 설정하세요.