# Webhook 수신 가이드

Gency는 상품 상태 변경 시 Webhook으로 이벤트를 전송합니다.

## 이벤트 유형

| 이벤트 | 설명 |
|--------|------|
| `product.pending_review` | 상품 생성 완료, 검수 대기 |
| `product.in_review` | 검수 진행 중 |
| `product.review_completed` | 검수 완료 |
| `product.deleted` | 상품 삭제됨 |
| `product.creation_failed` | 상품 생성 실패 |
| `test.ping` | 테스트 이벤트 |

## Webhook 등록

개발자 포털(developers.gency.ai)의 **Webhook** 메뉴에서:
1. **웹훅 추가하기** 클릭
2. **웹훅 명** 입력 (필수)
3. 수신 URL 입력 (HTTPS 필수)
4. 구독할 이벤트 선택
5. 생성 완료 시 Webhook Secret 발급

## 서명 검증

모든 Webhook 요청에는 HMAC-SHA256 서명이 포함됩니다.

### 헤더

| 헤더 | 설명 |
|------|------|
| `X-Webhook-Signature` | HMAC-SHA256 서명값 |
| `X-Webhook-Timestamp` | 요청 생성 타임스탬프 |

### 검증 방법

> **중요**: 서명 검증 시 `JSON.stringify()`나 `json.dumps()` 등으로 재직렬화하지 마세요. 언어/환경별 키 순서, 공백 처리 차이로 서명이 불일치할 수 있습니다. 반드시 수신한 HTTP **Raw Body 문자열**을 그대로 사용하세요.

```javascript
const crypto = require('crypto');

function verifyWebhook(rawBody, signature, timestamp, secret) {
  const message = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express 예시: raw body를 보존하려면 bodyParser 설정 필요
// app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString(); } }));
```

```python
import hmac
import hashlib

def verify_webhook(raw_body: str, signature: str, timestamp: str, secret: str) -> bool:
    message = f"{timestamp}.{raw_body}"
    expected = hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
```

> **보안 주의**: 타임스탬프를 확인하여 5분 이상 된 요청은 거부하세요 (리플레이 공격 방지).
