콘텐츠로 이동

서명 검증

모든 웹훅 전송에는 X-ParaSta-Signature 헤더가 포함됩니다.

X-ParaSta-Signature: t=1730000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t 는 ParaSta 가 요청에 서명한 시점의 Unix 타임스탬프(초)입니다.
  • v1 는 엔드포인트 서명 시크릿으로 키잉된 <t>.<raw body> 의 HMAC-SHA256 hex 다이제스트입니다.
  1. 헤더에서 tv1 를 파싱합니다.
  2. t 가 5분 이상 과거이거나 5분 이상 미래라면 거부합니다.
  3. expected = HMAC_SHA256(secret, t + '.' + raw_body) (hex) 를 계산합니다.
  4. expectedv1 를 상수 시간 비교(constant-time)로 비교합니다.
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(s => s.split('=')));
const t = parts.t, v1 = parts.v1;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
// Express:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verify(req.body.toString('utf8'), req.headers['x-parasta-signature'], process.env.WEBHOOK_SECRET);
if (!ok) return res.status(400).send('bad signature');
const event = JSON.parse(req.body.toString('utf8'));
// ...handle event
res.json({ received: true });
});
  • 검증 전에 본문을 파싱 — JSON 파싱은 공백을 변경해 HMAC 이 깨집니다. 항상 본문의 원본 바이트 로 검증하세요.
  • 단일 글로벌 시크릿을 여러 엔드포인트에 재사용 — 각 엔드포인트는 자체 시크릿을 가집니다.
  • 상수 시간 비교 대신 문자열 동등 비교 사용 — 타이밍 공격에 취약합니다.

Please contact your representative. 담당자에게 문의해주세요.