> ## Documentation Index
> Fetch the complete documentation index at: https://docs.legitmark.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook 处理指南

> 处理 Legitmark Webhook 事件的代码示例、最佳实践和实施指南。

本指南介绍如何构建可靠的 Webhook 处理程序，包含多种语言的代码示例和生产环境建议。

## 使用 TypeScript SDK

如果您使用 [TypeScript SDK](https://github.com/legitmark-eng/legitmark-typescript)（`v0.2.0+`），SDK 提供类型化的 Payload 和辅助函数来处理事件解析和状态检查。

```typescript theme={null}
import {
  parseWebhookEvent,
  isAuthentic,
  isCounterfeit,
  needsResubmission,
  isCancelled,
} from 'legitmark';

app.post('/webhooks/legitmark', express.json(), (req, res) => {
  const event = parseWebhookEvent(req.body);

  if (isAuthentic(event)) {
    markItemAsAuthentic(event.reference_id);
  } else if (isCounterfeit(event)) {
    flagItem(event.reference_id);
  } else if (needsResubmission(event)) {
    requestNewPhotos(event.reference_id, event.sides);
  } else if (isCancelled(event)) {
    closeCase(event.sr_uuid);
  }

  res.status(200).send('OK');
});
```

`parseWebhookEvent()` 验证 Payload 结构并返回类型化的 `LegitmarkWebhookEvent`。辅助函数检查特定的状态组合：

| 辅助函数                                | 返回 `true` 的条件                   |
| ----------------------------------- | ------------------------------- |
| `isAuthentic(event)`                | `COMPLETE + APPROVED` — 商品为正品   |
| `isCounterfeit(event)`              | `COMPLETE + REJECTED` — 商品非正品   |
| `isCancelled(event)`                | `CANCELLED` — 请求已取消             |
| `needsResubmission(event)`          | `media_rejected` — 图片需要重新上传     |
| `isQcApproved(event)`               | `QC + APPROVED` — 照片通过质量审核      |
| `isAuthenticationInProgress(event)` | `UNDERWAY + ASSIGNED` — 鉴定师正在工作 |

## 基础处理程序

如果您未使用 TypeScript SDK，以下是多种语言的示例。

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  app.post('/webhooks/legitmark', express.json(), (req, res) => {
    const event = req.body;

    switch (event.event_type) {
      case 'state_change':
        if (event.state.primary === 'COMPLETE') {
          updateAuthResult(event.reference_id, event.state.supplement);
        }
        break;

      case 'media_rejected':
        event.sides.forEach(side => {
          flagImageForReupload(event.reference_id, side.side, side.reason);
        });
        break;

      case 'invalidate_sr':
        cancelItem(event.reference_id, event.invalidation_reason.message);
        break;
    }

    res.status(200).send('OK');
  });
  ```

  ```python Python (Flask) theme={null}
  @app.route('/webhooks/legitmark', methods=['POST'])
  def handle_webhook():
      event = request.get_json()

      if event['event_type'] == 'state_change':
          if event['state']['primary'] == 'COMPLETE':
              update_auth_result(event['reference_id'], event['state']['supplement'])

      elif event['event_type'] == 'media_rejected':
          for side in event['sides']:
              flag_for_reupload(event['reference_id'], side['side'], side['reason'])

      elif event['event_type'] == 'invalidate_sr':
          cancel_item(event['reference_id'], event['invalidation_reason']['message'])

      return 'OK', 200
  ```
</CodeGroup>

## 最佳实践

<AccordionGroup>
  <Accordion title="立即返回 200">
    尽快确认 Webhook。如果处理逻辑复杂或需要调用外部服务，请先将任务加入队列并返回 `200`。
  </Accordion>

  <Accordion title="处理重复投递">
    您可能会多次收到同一事件。根据 Payload 字段构建去重键：

    * **`state_change`**：`sr_uuid` + `state.primary` + `state.supplement`（同一 SR 会发出多次状态变更）
    * **`media_rejected`**：`sr_uuid` + `event_type`
    * **`invalidate_sr`**：`sr_uuid` + `event_type`

    在生产环境中，请使用持久化存储（数据库、Redis）进行去重。
  </Accordion>

  <Accordion title="记录原始 Payload">
    保存完整的 Webhook Payload 用于调试。出现问题时，拥有原始数据可以大大加快诊断速度。
  </Accordion>

  <Accordion title="优雅处理未知事件类型">
    不要拒绝包含未识别 `event_type` 值的 Webhook。未来可能会添加新的事件类型。对任何事件都返回 `200`。
  </Accordion>
</AccordionGroup>

## 总结

您的 Webhook 处理程序应当：

* 使用 **HTTPS 端点** 并在 30 秒内返回 **200**
* 保证 **幂等性** — 使用 Payload 字段进行去重（不仅仅使用 `sr_uuid`，因为一个 SR 会发出多个事件）
* **接受未知事件类型** 而不报错（未来可能会添加新事件）
* **异步处理** 复杂逻辑以避免超时
