수신 서버 예제
서명 검증과 멱등 처리를 포함한 Express 수신 핸들러 예제입니다. 검증을 위해 **원문(raw body)**을 그대로 받는 점에 주의하세요.
import express from 'express'
import crypto from 'crypto'
const app = express()
const SECRET = process.env.PACTERY_WEBHOOK_SECRET
function verify(raw, header) {
const p = Object.fromEntries(header.split(',').map(s => s.trim().split('=')))
const expected = crypto.createHmac('sha256', SECRET).update(`${p.t}.${raw}`).digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1))) throw new Error('bad signature')
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) throw new Error('stale')
}
const seen = new Set() // 운영에선 DB/Redis로 대체
// raw body 보존: express.raw 사용
app.post('/webhooks/pactery', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8')
try {
verify(raw, req.header('Pactery-Signature'))
} catch {
return res.status(400).send('invalid signature')
}
const event = JSON.parse(raw)
res.sendStatus(200) // 먼저 200, 처리는 비동기로
if (seen.has(event.id)) return // 멱등: 이미 처리한 이벤트
seen.add(event.id)
switch (event.type) {
case 'participant.signed': /* ... */ break
case 'document.completed': /* 완료 후처리 */ break
}
})
app.listen(3000)