PacteryDevelopers

수신 서버 예제

서명 검증멱등 처리를 포함한 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)