Webhooks 연동 가이드
웹훅을 설정하여 주문 상태 변경 등의 이벤트를 실시간으로 수신하는 방법을 안내합니다.
웹훅이란?
웹훅은 특정 이벤트 발생 시 등록된 URL로 HTTP POST 요청을 보내는 방식입니다. 주문 상태를 주기적으로 폴링하지 않아도 실시간으로 변경 사항을 수신할 수 있습니다.
이벤트 발생 (주문 결제 완료)
→ SweetBook 서버가 등록된 URL로 HTTP POST 전송
→ 수신 서버가 200 OK 응답
→ 전송 완료 (SUCCESS)isTest 필드로 Sandbox 이벤트 여부를 구분할 수 있습니다.이벤트 종류
| 이벤트 | 발생 시점 | 활용 예시 |
|---|---|---|
order.paid | 주문 생성 (충전금 차감 완료) | 주문 접수 알림, 내부 시스템 동기화 |
order.confirmed | 제작 확정 | 제작 시작 안내 |
order.status_changed | 주문 상태 변경 | 상태 이력 추적, 대시보드 업데이트 |
order.shipped | 발송 완료 | 배송 추적 시작, 고객 알림 |
order.cancelled | 주문 취소 | 환불 처리, 재고 복원 |
웹훅 설정하기
1. 웹훅 등록
PUT /webhooks/config로 수신 URL을 등록합니다. 최초 등록 시 secretKey가 자동 생성됩니다.
curl -X PUT 'https://api.sweetbook.com/v1/webhooks/config' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"webhookUrl": "https://example.com/webhooks/sweetbook",
"events": ["order.paid", "order.shipped"],
"description": "주문 알림 수신"
}'| 필드 | 필수 | 설명 |
|---|---|---|
webhookUrl | Y | 수신 URL (HTTPS만 허용, 최대 500자) |
events | N | 구독할 이벤트 목록. null이면 전체 이벤트 구독 |
description | N | 설명 (최대 200자) |
whsk_a1b...). 발급 즉시 안전한 곳에 저장하세요.2. 설정 조회/수정
GET /webhooks/config로 현재 설정을 확인할 수 있습니다. 설정을 변경하려면 PUT /webhooks/config를 다시 호출하면 됩니다. 기존 secretKey는 유지되며, 수정 응답에는 전체 secretKey가 포함됩니다.
3. 웹훅 해제
DELETE /webhooks/config로 웹훅을 비활성화합니다. 소프트 삭제로 처리되며 전송 이력은 유지됩니다.
전송 헤더
웹훅 요청에는 다음 4개의 헤더가 포함됩니다.
| 헤더 | 설명 | 예시 |
|---|---|---|
X-Webhook-Signature | HMAC-SHA256 서명값 (hex) | a1b2c3d4... |
X-Webhook-Timestamp | 서명 생성 시점 (Unix timestamp, 초) | 1709280000 |
X-Webhook-Event | 이벤트 타입 | order.paid |
X-Webhook-Delivery | 전송 고유 ID | wh_abc123xyz |
서명 검증 (HMAC-SHA256)
수신한 웹훅 요청이 SweetBook에서 보낸 것인지 확인하려면 서명을 검증해야 합니다. 서명은 {timestamp}.{payload} 형식의 문자열을 secretKey로 HMAC-SHA256 해시한 값입니다.
서명 페이로드 = "{timestamp}.{JSON body}"
기대값 = HMAC-SHA256(secretKey, 서명 페이로드)
검증 = 기대값 == X-Webhook-Signature 헤더값JavaScript (Express.js)
const crypto = require('crypto');
function verifySignature(payload, signature, timestamp, secretKey) {
const signPayload = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secretKey)
.update(signPayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
app.post('/webhooks/sweetbook', express.json(), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const event = req.headers['x-webhook-event'];
const payload = JSON.stringify(req.body);
if (!verifySignature(payload, signature, timestamp, SECRET_KEY)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// 이벤트 처리
switch (event) {
case 'order.paid':
console.log('주문 결제 완료:', req.body);
break;
case 'order.shipped':
console.log('발송 완료:', req.body);
break;
}
res.status(200).json({ received: true });
});Python (Flask)
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = "whsk_your_secret_key"
def verify_signature(payload, signature, timestamp, secret_key):
sign_payload = f"{timestamp}.{payload}"
expected = hmac.new(
secret_key.encode(),
sign_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhooks/sweetbook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature')
timestamp = request.headers.get('X-Webhook-Timestamp')
event = request.headers.get('X-Webhook-Event')
payload = request.get_data(as_text=True)
if not verify_signature(payload, signature, timestamp, SECRET_KEY):
return jsonify({"error": "Invalid signature"}), 401
# 이벤트 처리
data = request.get_json()
if event == 'order.paid':
print(f"주문 결제 완료: {data}")
elif event == 'order.shipped':
print(f"발송 완료: {data}")
return jsonify({"received": True}), 200crypto.timingSafeEqual(), Python: hmac.compare_digest()테스트하기
POST /webhooks/test로 테스트 이벤트를 전송하여 수신 서버가 정상 동작하는지 확인할 수 있습니다. 샘플 데이터가 전송되며, 실제 주문 데이터는 포함되지 않습니다.
curl -X POST 'https://api.sweetbook.com/v1/webhooks/test' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"eventType": "order.paid"}'{
"success": true,
"message": "Success",
"data": {
"deliveryUid": "wh_abc123xyz",
"eventType": "order.paid",
"status": "SUCCESS",
"responseStatus": 200,
"responseBody": "{\"received\": true}"
}
}responseStatus와 responseBody를 확인하여 문제를 진단하세요.재시도 정책
전송 실패(2xx 외 응답 또는 타임아웃) 시 자동으로 최대 3회 재시도합니다.FAILED 상태인 건만 재시도 대상이며, PENDING 상태는 재시도되지 않습니다. HTTP 타임아웃은 30초입니다. 테스트 전송(is_test=true)은 재시도 없이 1회만 전송됩니다.
| 시도 | 대기 시간 |
|---|---|
| 1차 재시도 | 1분 후 |
| 2차 재시도 | 5분 후 |
| 3차 재시도 | 30분 후 |
3회 모두 실패하면 상태가 EXHAUSTED로 변경됩니다.
webhook.exhausted 이벤트가 트리거됩니다. 동일 알림은 1시간 쿨다운이 적용되어 과도한 알림이 발생하지 않습니다.전송 상태
| 상태 | 설명 |
|---|---|
PENDING | 전송 대기 또는 진행 중 |
SUCCESS | 전송 성공 (2xx 응답) |
FAILED | 전송 실패 (재시도 대기 중) |
EXHAUSTED | 최대 재시도(3회) 초과 |
전송 이력 확인
GET /webhooks/deliveries로 전송 이력을 조회할 수 있습니다. eventType과 status로 필터링할 수 있습니다.
# 실패한 전송 이력만 조회
curl 'https://api.sweetbook.com/v1/webhooks/deliveries?status=FAILED&limit=10' \
-H 'Authorization: Bearer YOUR_API_KEY'수신 서버 구현 시 유의사항
- 수신 서버는 200~299 상태 코드로 응답해야 합니다. 그 외 응답은 실패로 처리됩니다.
- 30초 이내에 응답해야 합니다. 시간이 오래 걸리는 작업은 비동기로 처리하세요.
X-Webhook-Signature를 반드시 검증하여 위변조 요청을 차단하세요.- 같은 이벤트가 중복 전송될 수 있습니다.
X-Webhook-Delivery헤더로 중복 여부를 확인하세요. - 수신 URL은 HTTPS만 허용됩니다.
관련 문서
- Orders API — 주문 생성 및 관리
- 인증 가이드 — API Key 발급