웹훅 시스템
Multi-SaaS Kit의 실시간 이벤트 알림 시스템입니다.
개요
웹훅을 통해 시스템 이벤트를 실시간으로 외부 서비스에 전달할 수 있습니다.
| 항목 | 설명 |
|---|---|
| 프로토콜 | HTTPS (TLS 1.2+) |
| 형식 | JSON |
| 인증 | HMAC-SHA256 서명 |
| 재시도 | 최대 5회 (지수 백오프) |
지원 이벤트
사용자 이벤트
| 이벤트 | 설명 | 트리거 시점 |
|---|---|---|
user.created | 사용자 생성 | 회원가입, 관리자 생성 |
user.updated | 사용자 정보 수정 | 프로필 수정, 권한 변경 |
user.deleted | 사용자 삭제 | Soft Delete 시 |
테넌트 이벤트
| 이벤트 | 설명 | 트리거 시점 |
|---|---|---|
tenant.created | 테넌트 생성 | 새 테넌트 등록 |
tenant.updated | 테넌트 정보 수정 | 설정 변경 |
tenant.suspended | 테넌트 정지 | 결제 실패, 관리자 조치 |
tenant.activated | 테넌트 활성화 | 정지 해제 |
구독 이벤트 (플러그인)
| 이벤트 | 설명 | 트리거 시점 |
|---|---|---|
subscription.created | 구독 생성 | 첫 구독 |
subscription.renewed | 구독 갱신 | 자동/수동 갱신 |
subscription.canceled | 구독 취소 | 취소 요청 |
subscription.expired | 구독 만료 | 기간 종료 |
웹훅 등록
POST /api/v1/webhooks
새 웹훅 엔드포인트 등록
Request
POST /api/v1/webhooks
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"url": "https://your-app.com/webhooks/multi-saas",
"events": ["user.created", "user.updated", "tenant.created"],
"secret": "your-webhook-secret",
"active": true,
"description": "메인 웹훅 엔드포인트"
}
Response
{
"success": true,
"data": {
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/multi-saas",
"events": ["user.created", "user.updated", "tenant.created"],
"active": true,
"created_at": "2024-01-20T00:00:00Z"
}
}
페이로드 구조
기본 구조
모든 웹훅 페이로드는 동일한 기본 구조를 따릅니다:
{
"id": "evt_1234567890abcdef",
"type": "user.created",
"created_at": "2024-01-20T12:00:00Z",
"tenant_id": 1,
"api_version": "v1",
"data": {
// 이벤트별 데이터
}
}
| 필드 | 타입 | 설명 |
|---|---|---|
id | string | 이벤트 고유 ID |
type | string | 이벤트 유형 |
created_at | string | 이벤트 발생 시간 (ISO 8601) |
tenant_id | integer | 테넌트 ID |
api_version | string | API 버전 |
data | object | 이벤트 데이터 |
이벤트별 페이로드
user.created
{
"id": "evt_usr_123",
"type": "user.created",
"created_at": "2024-01-20T12:00:00Z",
"tenant_id": 1,
"api_version": "v1",
"data": {
"user": {
"id": 10,
"name": "홍길동",
"email": "hong@example.com",
"permission_level": 6,
"created_at": "2024-01-20T12:00:00Z"
}
}
}
tenant.created
{
"id": "evt_ten_456",
"type": "tenant.created",
"created_at": "2024-01-20T12:00:00Z",
"tenant_id": 5,
"api_version": "v1",
"data": {
"tenant": {
"id": 5,
"name": "New Company",
"slug": "new-company",
"plan": "starter",
"owner": {
"id": 15,
"email": "admin@new-company.com"
},
"created_at": "2024-01-20T12:00:00Z"
}
}
}
서명 검증
서명 헤더
모든 웹훅 요청에는 서명 헤더가 포함됩니다:
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
X-Webhook-Signature: sha256=5d0e...
X-Webhook-Timestamp: 1705752000
X-Webhook-Event-Id: evt_1234567890
서명 생성 방법
signature = HMAC-SHA256(
secret,
timestamp + "." + request_body
)
PHP 검증 예제
<?php
class WebhookController extends Controller
{
public function handle(Request $request)
{
$payload = $request->getContent();
$signature = $request->header('X-Webhook-Signature');
$timestamp = $request->header('X-Webhook-Timestamp');
// 타임스탬프 검증 (5분 이내)
if (abs(time() - (int)$timestamp) > 300) {
return response()->json(['error' => 'Invalid timestamp'], 400);
}
// 서명 검증
$expectedSignature = 'sha256=' . hash_hmac(
'sha256',
$timestamp . '.' . $payload,
config('services.multi-saas.webhook_secret')
);
if (!hash_equals($expectedSignature, $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
// 이벤트 처리
$event = json_decode($payload, true);
$this->processEvent($event);
return response()->json(['received' => true]);
}
private function processEvent(array $event)
{
match($event['type']) {
'user.created' => $this->handleUserCreated($event['data']),
'tenant.created' => $this->handleTenantCreated($event['data']),
default => null,
};
}
}
JavaScript (Node.js) 검증 예제
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
// 타임스탬프 검증 (5분 이내)
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
throw new Error('Invalid timestamp');
}
// 서명 생성 및 비교
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
throw new Error('Invalid signature');
}
return JSON.parse(payload);
}
재시도 정책
재시도 조건
| HTTP 상태 코드 | 재시도 여부 |
|---|---|
| 2xx | ❌ 성공 |
| 3xx | ❌ 재시도 안 함 |
| 4xx (429 제외) | ❌ 재시도 안 함 |
| 429 | ✅ 재시도 |
| 5xx | ✅ 재시도 |
| 타임아웃 | ✅ 재시도 |
재시도 간격 (지수 백오프)
| 시도 | 대기 시간 |
|---|---|
| 1차 | 즉시 |
| 2차 | 30초 |
| 3차 | 2분 |
| 4차 | 10분 |
| 5차 | 1시간 |
| 이후 | 실패 처리 |
실패 알림
5회 재시도 실패 시:
- 웹훅 비활성화 (
active: false) - 관리자 이메일 알림
- 대시보드에 경고 표시
웹훅 관리
GET /api/v1/webhooks
등록된 웹훅 목록 조회
GET /api/v1/webhooks
Authorization: Bearer YOUR_TOKEN
PUT /api/v1/webhooks/:id
웹훅 설정 수정
PUT /api/v1/webhooks/wh_abc123
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"events": ["user.created", "user.updated"],
"active": true
}
DELETE /api/v1/webhooks/:id
웹훅 삭제
DELETE /api/v1/webhooks/wh_abc123
Authorization: Bearer YOUR_TOKEN
POST /api/v1/webhooks/:id/test
테스트 이벤트 전송
POST /api/v1/webhooks/wh_abc123/test
Authorization: Bearer YOUR_TOKEN
웹훅 로그
GET /api/v1/webhooks/:id/logs
웹훅 전송 로그 조회
GET /api/v1/webhooks/wh_abc123/logs?page=1
Authorization: Bearer YOUR_TOKEN
{
"success": true,
"data": [
{
"id": "log_123",
"event_id": "evt_usr_123",
"event_type": "user.created",
"status": "success",
"response_code": 200,
"response_time_ms": 150,
"attempts": 1,
"created_at": "2024-01-20T12:00:00Z"
},
{
"id": "log_124",
"event_id": "evt_ten_456",
"event_type": "tenant.created",
"status": "failed",
"response_code": 500,
"response_time_ms": 30000,
"attempts": 5,
"last_error": "Connection timeout",
"created_at": "2024-01-20T12:05:00Z"
}
]
}
베스트 프랙티스
1. 빠른 응답
// 좋음: 즉시 응답 후 비동기 처리
public function handle(Request $request)
{
// 서명 검증
$this->verifySignature($request);
// 큐에 작업 추가
ProcessWebhookJob::dispatch($request->all());
// 즉시 200 응답
return response()->json(['received' => true]);
}
2. 멱등성 보장
// 이벤트 ID로 중복 처리 방지
public function processEvent($event)
{
$eventId = $event['id'];
if (WebhookLog::where('event_id', $eventId)->exists()) {
return; // 이미 처리됨
}
// 처리 로직...
WebhookLog::create(['event_id' => $eventId, ...]);
}
3. 오류 처리
- 타임아웃: 30초 이내 응답
- 재시도 고려: 멱등성 보장
- 로깅: 모든 웹훅 요청 기록