웹훅
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 검증
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 검증
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_retries | 3 | 0 이상 정수 |
retry_delay_seconds | 60 | 초 단위 정수 |
retry_backoff_type | exponential | fixed / linear / exponential |
기본 설정(3회, 60초, exponential)이면 대략 1분 / 2분 / 4분 간격으로 재시도됩니다. 콘솔 → 웹훅 상세 → 설정에서 조정할 수 있습니다.
모범 사례
1. 빠르게 응답
웹훅 핸들러는 5초 내에 200 응답을 반환해야 합니다:
// ✅ 빠른 응답
app.post('/webhooks', async (req, res) => {
// 즉시 200 응답
res.status(200).send('OK')
// 백그라운드에서 처리
processEventAsync(req.body)
})2. 멱등성 보장
같은 이벤트가 여러 번 전달될 수 있으므로 멱등성을 보장하세요:
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. 로깅
디버깅을 위해 모든 이벤트를 로깅:
app.post('/webhooks', (req, res) => {
console.log({
eventId: req.body.id,
type: req.body.type,
timestamp: req.body.timestamp
})
// 처리 로직...
})