서명 검증
웹훅 요청이 진짜 팩터리에서 왔는지, 변조되지 않았는지 Pactery-Signature 헤더로 검증합니다.
헤더 형식
Pactery-Signature: t=1717570000, v1=5a2f8c...
t: 서명 생성 시각(epoch 초)v1:{t}.{raw_body}를 웹훅 비밀키(whsec_...)로 HMAC-SHA256 한 값
검증 절차
t와 원문 본문을.으로 이어 서명 대상 문자열을 만든다.- 비밀키로 HMAC-SHA256을 계산한다.
- 결과를
v1과 상수 시간 비교한다. t가 현재 시각과 5분 이상 차이 나면 재생 공격으로 보고 거절한다.
import crypto from 'crypto'
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(p => p.trim().split('=')))
const signed = `${parts.t}.${rawBody}`
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex')
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
if (!ok) throw new Error('bad signature')
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) throw new Error('stale')
}
검증은 반드시 **파싱 전 원문(raw body)**으로 해야 합니다. JSON으로 다시 직렬화한 문자열은 바이트가 달라질 수 있습니다.