Step 6: 웹훅으로 상태 추적
웹훅을 등록하고, 주문 상태 변경 이벤트를 실시간으로 수신하는 방법을 안내합니다.
웹훅 설정
PUT /webhooks/config로 웹훅 URL과 수신할 이벤트를 등록합니다.
bash
curl -X PUT 'https://api-sandbox.sweetbook.com/v1/webhooks/config' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://your-server.com/webhooks/sweetbook",
"events": [
"order.created",
"production.confirmed",
"production.started",
"production.completed",
"shipping.departed",
"order.cancelled"
]
}'설정 조회
bash
curl -X GET 'https://api-sandbox.sweetbook.com/v1/webhooks/config' \
-H 'Authorization: Bearer YOUR_API_KEY'테스트 이벤트 전송
POST /webhooks/test로 등록된 URL에 테스트 이벤트를 보내 정상 수신 여부를 확인할 수 있습니다.
bash
curl -X POST 'https://api-sandbox.sweetbook.com/v1/webhooks/test' \
-H 'Authorization: Bearer YOUR_API_KEY'서명 검증 (HMAC-SHA256)
웹훅 요청의 진위를 확인하기 위해 HMAC-SHA256 서명을 검증해야 합니다. 요청 헤더에 다음 정보가 포함됩니다.
| 헤더 | 설명 |
|---|---|
X-Webhook-Event | 이벤트 타입 (예: order.created) |
X-Webhook-Delivery | 고유 전송 ID |
X-Webhook-Timestamp | Unix 타임스탬프 |
X-Webhook-Signature | 서명 값 (sha256=... 형식) |
서명 검증 방식: {timestamp}.{payload} 문자열을 시크릿 키로 HMAC-SHA256 해싱하여 비교합니다.
JavaScript 예시
javascript
const crypto = require('crypto');
function verifyWebhookSignature(payload, headers, secret) {
const timestamp = headers['x-webhook-timestamp'];
const signature = headers['x-webhook-signature'];
const expectedSig = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}
// Express.js 예시
app.post('/webhooks/sweetbook', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body.toString();
const isValid = verifyWebhookSignature(payload, req.headers, WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log('Event:', event.event_type, event.data);
res.status(200).send('OK');
});Python 예시
python
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, headers: dict, secret: str) -> bool:
timestamp = headers.get('X-Webhook-Timestamp', '')
signature = headers.get('X-Webhook-Signature', '')
message = f"{timestamp}.{payload.decode('utf-8')}"
expected_sig = 'sha256=' + hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_sig)
# Flask 예시
@app.route('/webhooks/sweetbook', methods=['POST'])
def handle_webhook():
payload = request.get_data()
if not verify_webhook_signature(payload, request.headers, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
print(f"Event: {event['event_type']}", event['data'])
return 'OK', 200공통 페이로드 구조
모든 웹훅 이벤트는 다음과 같은 공통 구조로 전달됩니다.
json
{
"event_uid": "evt_abc123def456",
"event_type": "order.created",
"created_at": "2025-10-21T03:00:00Z",
"data": {
...
}
}| 필드 | 설명 |
|---|---|
event_uid | 이벤트 고유 ID |
event_type | 이벤트 타입 |
created_at | 이벤트 생성 시각 (ISO 8601) |
data | 이벤트별 상세 데이터 |
웹훅 이벤트 목록
1. order.created — 주문 생성/결제완료
json
{
"event_uid": "evt_abc123def456",
"event_type": "order.created",
"created_at": "2025-10-21T03:00:00Z",
"data": {
"order_uid": "ord_a1b2c3d4e5f6",
"order_status": "PAID",
"total_amount": 15500,
"item_count": 1,
"ordered_at": "2025-10-21T03:00:00Z"
}
}2. production.confirmed — 제작 확정
json
{
"event_uid": "evt_def456ghi789",
"event_type": "production.confirmed",
"created_at": "2025-10-22T09:00:00Z",
"data": {
"order_uid": "ord_a1b2c3d4e5f6",
"order_status": "CONFIRMED",
"print_day": "2025-10-23",
"confirmed_at": "2025-10-22T09:00:00Z"
}
}3. production.started — 제작 시작
json
{
"event_uid": "evt_ghi789jkl012",
"event_type": "production.started",
"created_at": "2025-10-23T08:00:00Z",
"data": {
"order_uid": "ord_a1b2c3d4e5f6",
"order_status": "IN_PRODUCTION",
"print_day": "2025-10-23",
"started_at": "2025-10-23T08:00:00Z"
}
}4. production.completed — 제작 완료
json
{
"event_uid": "evt_jkl012mno345",
"event_type": "production.completed",
"created_at": "2025-10-25T16:00:00Z",
"data": {
"order_uid": "ord_a1b2c3d4e5f6",
"order_status": "PRODUCTION_COMPLETE",
"completed_at": "2025-10-25T16:00:00Z"
}
}5. shipping.departed — 배송 출발
json
{
"event_uid": "evt_mno345pqr678",
"event_type": "shipping.departed",
"created_at": "2025-10-26T10:00:00Z",
"data": {
"order_uid": "ord_a1b2c3d4e5f6",
"item_uid": "itm_x1y2z3w4",
"tracking_number": "1234567890",
"tracking_carrier": "한진택배",
"shipped_at": "2025-10-26T10:00:00Z"
}
}6. order.cancelled — 주문 취소
json
{
"event_uid": "evt_pqr678stu901",
"event_type": "order.cancelled",
"created_at": "2025-10-21T04:00:00Z",
"data": {
"order_uid": "ord_a1b2c3d4e5f6",
"order_status": "CANCELLED_REFUND",
"cancel_reason": "고객 요청에 의한 취소",
"refund_amount": 15500,
"cancelled_at": "2025-10-21T04:00:00Z"
}
}주문 상태 ↔ 웹훅 이벤트 매핑
| 주문 상태 | 웹훅 이벤트 | 설명 |
|---|---|---|
PAID | order.created | 주문 생성 / 결제완료 |
CONFIRMED | production.confirmed | 제작 확정 |
IN_PRODUCTION | production.started | 제작 시작 |
PRODUCTION_COMPLETE | production.completed | 제작 완료 |
SHIPPED | shipping.departed | 배송 출발 (송장번호 포함) |
CANCELLED_REFUND | order.cancelled | 주문 취소 및 환불 |
재시도 정책
웹훅 전송이 실패하면 (비-2xx 응답 또는 타임아웃) 최대 3회까지 자동으로 재시도합니다.
| 시도 | 대기 시간 | 설명 |
|---|---|---|
| 1차 재시도 | 1분 후 | 첫 번째 실패 후 |
| 2차 재시도 | 5분 후 | 두 번째 실패 후 |
| 3차 재시도 | 30분 후 | 세 번째 실패 후 (최종) |
3회 재시도 후에도 전송에 실패하면 해당 이벤트는 더 이상 전송되지 않습니다. 파트너 포털의 웹훅 로그에서 실패 내역을 확인할 수 있습니다.
폴링 대안
웹훅 구현이 어려운 경우 GET /orders/{orderUid}로 주문 상태를 주기적으로 조회하는 폴링 방식을 사용할 수 있습니다.
bash
curl -X GET 'https://api-sandbox.sweetbook.com/v1/orders/{orderUid}' \
-H 'Authorization: Bearer YOUR_API_KEY'폴링 시 Rate Limit을 고려하여 적절한 간격(예: 5~10분)을 두고 조회하세요. 실시간성이 중요한 경우 웹훅 사용을 권장합니다.
다음 단계
- Step 7: 운영 환경 전환 체크리스트 — Sandbox에서 Live로 전환하기
- Webhooks API 레퍼런스 — 전체 Webhooks API 상세 문서
- Webhook Events 레퍼런스 — 이벤트 타입별 상세 스키마