본문으로 건너뛰기

웹훅

Connect Base 웹훅 심화 가이드입니다.

개요

웹훅을 사용하면 Connect Base에서 발생하는 이벤트를 실시간으로 외부 시스템에 전달할 수 있습니다.

지원 이벤트

웹훅 entity 의 event_types 배열에 다음 문자열을 등록합니다 (ent webhook.go 주석 기준).

이벤트설명source 타입
data.created테이블 데이터 생성table
data.updated테이블 데이터 수정table
data.deleted테이블 데이터 삭제table
file.uploaded파일/비디오 스토리지 업로드storage_file / video

📌 user.* (사용자 가입/수정/삭제) 이벤트는 일반 웹훅에 아직 노출돼 있지 않습니다. OAuth Provider 측 이벤트는 별도의 OAuth Webhook(/v1/integrations/providers/:provider_app_id/event-catalog) 으로 분리되어 있습니다.

서명 검증

모든 일반 웹훅 요청은 X-Webhook-Signature: sha256={hex} 헤더가 동봉됩니다 (데이터-체인지 / OAuth Provider 웹훅은 별도의 X-ConnectBase-Signature 사용 — 헤더 이름이 다릅니다).

HMAC 입력은 JSON.stringify 가 아닌 raw 요청 본문 이므로, 프레임워크가 본문을 파싱하기 전 raw bytes 를 확보해야 합니다 (Express bodyParser.raw / Fastify addContentTypeParser).

Node.js 검증

javascript
import crypto from 'crypto'
import express from 'express'

const app = express()
// raw body 보존
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.headers['x-webhook-signature'] || ''
  if (!header.startsWith('sha256=')) {
    return res.status(401).send('Missing signature')
  }
  const received = header.slice('sha256='.length)
  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body) // raw Buffer
    .digest('hex')

  const a = Buffer.from(received, 'hex')
  const b = Buffer.from(expected, 'hex')
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send('Invalid signature')
  }

  const event = JSON.parse(req.body.toString('utf8'))
  console.log('Event:', event.type)
  res.status(200).send('OK')
})

Python 검증

python
import hmac, hashlib
from flask import Flask, request, abort

app = Flask(__name__)

@app.post('/webhooks')
def webhook():
    header = request.headers.get('X-Webhook-Signature', '')
    if not header.startswith('sha256='):
        abort(401)
    received = header[len('sha256='):]
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.get_data(),   # raw bytes
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(received, expected):
        abort(401)
    event = request.get_json()
    return ('OK', 200)

재시도 정책

재시도 횟수 / 대기 시간 / 백오프 방식은 웹훅별로 설정 가능합니다 (ent 스키마 기본값과 옵션).

필드기본값옵션
max_retries30 이상 정수
retry_delay_seconds60초 단위 정수
retry_backoff_typeexponentialfixed / linear / exponential

기본 설정(3회, 60초, exponential)이면 대략 1분 / 2분 / 4분 간격으로 재시도됩니다. 콘솔 → 웹훅 상세 → 설정에서 조정할 수 있습니다.

모범 사례

1. 빠르게 응답

웹훅 핸들러는 5초 내에 200 응답을 반환해야 합니다:

javascript
// ✅ 빠른 응답
app.post('/webhooks', async (req, res) => {
  // 즉시 200 응답
  res.status(200).send('OK')

  // 백그라운드에서 처리
  processEventAsync(req.body)
})

2. 멱등성 보장

같은 이벤트가 여러 번 전달될 수 있으므로 멱등성을 보장하세요:

javascript
app.post('/webhooks', async (req, res) => {
  const eventId = req.body.id

  // 이미 처리된 이벤트인지 확인
  if (await isProcessed(eventId)) {
    return res.status(200).send('Already processed')
  }

  await processEvent(req.body)
  await markAsProcessed(eventId)

  res.status(200).send('OK')
})

3. 로깅

디버깅을 위해 모든 이벤트를 로깅:

javascript
app.post('/webhooks', (req, res) => {
  console.log({
    eventId: req.body.id,
    type: req.body.type,
    timestamp: req.body.timestamp
  })

  // 처리 로직...
})