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

# Webhook 事件

> Webhook 事件类型目录，以及每种事件携带的载荷。

<Note>
  此处的载荷依据 Maple 实际发出的内容编写，但尚未有正式的 schema 类型定义，后续可能新增字段。请进行防御式校验（忽略未知字段），并将 `GET /v1/orders/{orderId}` 视为订单当前状态的权威来源。事件类型的最新列表始终可通过 `GET /v1/webhook_event_types` 获取。
</Note>

每个 webhook 都以相同的[签名信封](/zh/developer-api/concepts/webhooks)投递；只有 `data` 会随 `type` 不同而变化。本页介绍每一种事件类型。有关签名、投递、重试和重放，请参阅[Webhooks](/zh/developer-api/concepts/webhooks)。

## 订单事件

订单 webhook 分为两类，分清两者是用好它们的关键：

* **`order.notification` 是您据以履约的事件。** 它把订单内容交给您处理，在交接时触发一次，其投递被视为关键投递。请围绕它构建您的接受 / 拒绝 / 备好 / 完成流程。
* **`order.created`、`order.paid` 和 `order.cancelled` 是生命周期事件。** 每个事件以摘要数据报告订单生命中的一个时刻，用于知悉和对账。由于 `order.notification` 只触发一次且不会更新，您要通过这些事件了解该快照之前或之后发生的事情，最重要的是取消，或稍后才完成的支付。

有关这些事件在一笔订单的整个生命周期中的触发顺序，请参阅[Webhook 何时触发](/zh/developer-api/concepts/order-lifecycle)。

| 类型 | 触发时机 | 订阅它的目的 | `data` |
| - | - | - | - |
| `order.validation_requested` | 在通知之前，Maple 请您确认订单可以履约（仅当您订阅时） | 在向顾客收款之前预先校验可售状态和价格 | 完整订单内容（与 `order.notification` 结构相同） |
| `order.notification` | 订单在校验和支付把关之后交给您履约 | **接收并履约订单**，集成的核心 | 完整订单内容（见下文） |
| `order.created` | 订单创建时，早于通知，且内容比通知更精简 | 为分析镜像新订单；发现从未到达您的订单（例如在校验阶段被拒绝） | `order_id`、`location_id`、`status`、`total`、`items`、`created_at` |
| `order.paid` | 支付完成，可能发生在交接\_之后\_（到店付款、链接支付） | 无需轮询即可对账支付 | `order_id`、`location_id`、`status`、`amount`、`payment_method`、`payment_source` |
| `order.cancelled` | 顾客或门店取消，通常发生在您收到通知\_之后\_ | 停止履约已取消的订单 | `order_id`、`location_id`、`status`、`reason` |

### 我应该订阅哪些订单事件？

* **履约订单（POS 系统）：** 订阅 `order.notification` 和 `order.cancelled`，接收订单，并在订单被取消时停止。如果您需要预先校验，再加上 `order.validation_requested`。
* **报表或对账（OMS、控制台、分析）：** 还需订阅 `order.created` 和 `order.paid`，以镜像订单和支付的生命周期。

### `order.notification` 载荷

`order.notification`（以及 `order.validation_requested`）在 `data` 中携带订单内容，足以开始履约，结构遵循 `GET /v1/orders/{orderId}`：

```json theme={null}
{
  "order_id": "ord_...",
  "location_id": "str_...",
  "fulfillment_type": "delivery",
  "scheduled_for": null,
  "delivery_address": {
    "street": "123 Main Street",
    "unit": "Apt 4B",
    "city": "New York",
    "state": "NY",
    "zip": "10001",
    "notes": "Leave at the side door"
  },
  "customer": { "name": "Alex", "phone": "+14155551234", "phone_last_four": "1234" },
  "line_items": [
    {
      "name": "Latte",
      "menu_entity_id": "itm-latte",
      "quantity": 1,
      "base_price": 450,
      "modifiers": [{ "name": "Oat milk", "menu_entity_id": "mod-oat", "quantity": 1, "price": 75 }]
    }
  ],
  "totals": { "currency": "USD", "subtotal": 525, "tax": 42, "surcharge": 0, "tip": 0, "delivery_fee": 599, "delivery_tip": 0, "total": 1166 }
}
```

这是订单的**内容**，而非实时状态，其中没有 `status` 或 `payment`。如需权威的当前状态（状态、支付、每项税额、时间戳），请获取 [`GET /v1/orders/{orderId}`](/zh/developer-api/concepts/order-lifecycle)。生命周期事件只携带上面列出的摘要字段。

对于尽快（ASAP）订单，`scheduled_for` 为 `null`。如果有值（ISO 8601，UTC），则表示顾客选择了该取餐/配送时间。通知仍会立即到达，因此请按 `scheduled_for` 备餐，而不是在收到时就开始。

对于自取订单，以及没有存储地址的配送订单，`delivery_address` 为 `null`。存在时，六个键全部包含；`notes` 包含配送说明，`unit` 或 `notes` 可能为 `null`。

### 并非状态推送

Maple **不会**为每一次状态变化发出 webhook。由您自己推动的状态变化（`accept`、`ready`、`complete`）不会回传，到达 `IN_DELIVERY` 或支付变为 `refunded` / `voided` / `failed` 也没有对应事件。请读取订单资源获取当前状态，参阅[订单生命周期](/zh/developer-api/concepts/order-lifecycle)。

## 预订事件

预订事件会投递给每个已获授权访问该门店、其允许的权限范围包含 `bookings:read` 且订阅与事件类型匹配的活跃应用。它们不要求应用拥有 POS 连接。该门店的预订灰度发布开关必须已启用。

| 类型 | 报告的变化 |
| - | - |
| `booking.created` | 预订已创建，或首次从其提供方处观察到 |
| `booking.confirmed` | 请求已确认 |
| `booking.updated` | 预订已变更 |
| `booking.cancelled` | 预订已取消 |
| `booking.declined` | 请求已拒绝 |
| `booking.seated` | 客人已入座 |
| `booking.completed` | 服务已完成 |
| `booking.no_show` | 客人被标记为未到店 |

这八种事件使用相同的 `data` 结构。嵌入的 `booking` 与 GET 资源一致：

```json theme={null}
{
  "location_id": "str_example",
  "previous_status": null,
  "booking": {
    "object": "booking",
    "id": "bkg_example",
    "livemode": false,
    "created": 1789063200,
    "version": 1,
    "location_id": "str_example",
    "status": "confirmed",
    "service_state": null,
    "confirmation_code": "AB1234",
    "starts_at": "2026-09-12T22:00:00.000Z",
    "starts_at_local": "2026-09-12T18:00",
    "service_date": "2026-09-12",
    "duration_minutes": 90,
    "party_size": 2,
    "guest": { "first_name": "Alex", "last_name": "Quinn", "phone": "+12125551234", "phone_last_four": "1234", "email": null },
    "notes": null,
    "channel": "api_partner",
    "provider": "maple",
    "cancellation_policy": null,
    "updated_at": "2026-09-10T18:01:00.000Z"
  }
}
```

载荷是**首次分发时的最新预订状态**，而不是该次状态变化的不可变快照。`previous_status` 为 `null`。因此，事件名称描述的变化可能早于嵌入预订的当前状态。某个接收方的事件一旦被记录，重试时会保持该载荷和事件标识不变。

每个原始 outbox 事件在每个应用/环境下都有稳定的标识。即使两次变更在同一预订版本上分发，对同一预订的两次变更仍然是各自独立的事件。请按事件 ID 去重，并按预订的 `id`/`version` 对账；不要依赖投递顺序。独立的开发者消费方不会推进宾客沟通游标。此版本不转发任何楼面事件；候位变化有自己的[事件](#候位事件)。

某个接收方入队失败，不会阻止其他符合条件的应用或订阅入队。重试会复用同一订阅的事件标识。预订事件的发布最多进行三次分发尝试，每次尝试都有有限次数的内部重试。格式错误的内部事件和已用尽分发尝试的事件会被保留，供运维人员排查，而不会阻塞后续事件或无限重试。这与 [HTTP webhook 投递重试](/zh/developer-api/concepts/webhooks)是两回事。投递出现缺口后，请使用 `GET /v1/bookings?updated_since=…` 对账当前预订状态。

## 候位事件

候位事件会送达与预订事件相同的应用：每个已获授权访问该门店、其允许的权限范围包含 `bookings:read` 且订阅与事件类型匹配的活跃应用。只要该门店的预订灰度发布开关已启用，Maple 原生门店就会发送这些事件。请参阅[运行候位](/zh/developer-api/guides/run-the-waitlist)。

| 类型 | 报告的变化 |
| - | - |
| `waitlist_entry.created` | 一组客人加入候位 |
| `waitlist_entry.notified` | Maple 已发短信通知该组客人餐桌已备好 |
| `waitlist_entry.seated` | 该组客人已入座，生成一条散客（walk-in）预订 |
| `waitlist_entry.removed` | 客人自行离开（`removed_by_guest`），或店方将其移除（`removed_by_venue`） |
| `waitlist_entry.expired` | 已通知的客人未在时限内回复 |

这五种事件使用相同的 `data` 结构。嵌入的 `waitlist_entry` 与 `GET /v1/waitlist_entries/{entryId}` 一致：

```json theme={null}
{
  "location_id": "str_example",
  "waitlist_entry": {
    "object": "waitlist_entry",
    "id": "bkw_example",
    "livemode": false,
    "created": 1789063200,
    "location_id": "str_example",
    "status": "seated",
    "party_size": 2,
    "position": null,
    "quoted_wait": { "parties_ahead": 1, "wait_minutes": { "min": 15, "max": 30 } },
    "joined_at": "2026-09-10T18:00:00.000Z",
    "notified_at": "2026-09-10T18:20:00.000Z",
    "booking_id": "bkg_example",
    "channel": "api_partner",
    "guest": { "first_name": "Alex", "last_name": "Quinn", "phone": "+12125551234", "phone_last_four": "1234", "email": null }
  }
}
```

与预订事件一样，载荷是**首次分发时的最新候位条目状态**：在客人入座之后才送达的 `waitlist_entry.created`，显示的已经是 `seated`。请根据事件类型判断发生了什么，根据 `waitlist_entry.status` 判断条目当前所处的状态。每次变化在每个应用和环境下都有各自的事件 ID；请按事件 ID 去重，不要依赖投递顺序。投递尝试、重试以及格式错误内部事件的隔离，均遵循上述预订事件的规则。入座还会创建一条散客预订，该预订由您订阅的预订事件报告。

## 门店（location）事件

这些事件跟踪您对门店的访问权限，即[连接生命周期](/zh/developer-api/concepts/entities)：

| 类型 | 触发时机 | `data`（示意） |
| - | - | - |
| `store.provisioned` | 商户授予您的应用对某门店的访问权限 | `location_id` |
| `store.deprovisioned` | 授权被撤销；您已失去对该门店的访问权限 | `location_id` |
| `store.status.changed` | 您与某门店的连接发生变化 | `location_id`、`status`（`connected` / `disconnected`） |

## 菜单事件

| 类型 | 触发时机 | `data`（示意） |
| - | - | - |
| `menu.sync.completed` | 某门店的菜单同步完成 | `location_id` |
| `menu.sync.failed` | 某门店的菜单同步失败 | `location_id` |

请参阅[发布菜单](/zh/developer-api/guides/publish-menu)。

## 测试事件

`webhook.test` 通过 `POST /v1/webhook_subscriptions/{id}/test` 按需投递。请在任何真实流量到来之前用它验证您的处理程序。真实业务活动绝不会发出它。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.