> ## 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 events

> The catalog of webhook event types and the payload each one carries.

> [!NOTE]
>
> Payloads here are documented from what Maple emits, but they aren’t formally schema-typed yet — new fields may be added. Validate defensively (ignore unknown fields), and treat `GET /v1/orders/{orderId}` as the authoritative source for an order’s current state. The live list of event types is always at `GET /v1/webhook_event_types`.

Every webhook is delivered in the same [signed envelope](https://docs.maple.inc/developer-api/concepts/webhooks.md#the-event-envelope); only the `data` differs by `type`. This page documents each event type. For signing, delivery, retries, and replay, see [Webhooks](https://docs.maple.inc/developer-api/concepts/webhooks.md).

## Order events

Order webhooks come in two kinds, and knowing which is which is the key to using them well:

- **`order.notification` is the event you fulfill against.** It hands you the order’s content to act on, fires once at handoff, and its delivery is treated as critical. Build your accept / deny / ready / complete loop on it.
- **`order.created`, `order.paid`, and `order.cancelled` are lifecycle events.** Each reports one moment in the order’s life with summary data, for awareness and reconciliation. Because `order.notification` fires only once and never updates, these are how you learn about things that happen before or after that snapshot — most importantly a cancellation, or payment settling later.

For the order in which these fire across one order’s life, see [When the webhooks fire](https://docs.maple.inc/developer-api/concepts/order-lifecycle.md#when-the-webhooks-fire).

| Type                         | Fires when                                                                                       | Subscribe to it to…                                                                              | `data`                                                                            |
| ---------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| `order.validation_requested` | Before notification — Maple asks you to confirm the order is fulfillable (only if you subscribe) | Pre-validate availability and pricing before the customer is charged                             | Full order content (same shape as `order.notification`) plus `validation_id`      |
| `order.notification`         | The order is handed to you to fulfill, after validation and payment gating                       | **Receive and fulfill orders** — the core of an integration                                      | Full order content (see below)                                                    |
| `order.created`              | An order is created — earlier than, and thinner than, the notification                           | Mirror new orders for analytics; catch orders that never reach you (e.g. rejected at validation) | `order_id`, `location_id`, `status`, `total`, `items`, `created_at`               |
| `order.paid`                 | Payment settles — which can be *after* the handoff (pay-in-store, pay-by-link)                   | Reconcile payment without polling                                                                | `order_id`, `location_id`, `status`, `amount`, `payment_method`, `payment_source` |
| `order.cancelled`            | The customer or store cancels — often *after* you’ve been notified                               | Stop fulfilling a cancelled order                                                                | `order_id`, `location_id`, `status`, `reason`                                     |

### Which order events should I subscribe to?

- **Fulfilling orders (a POS):** `order.notification` and `order.cancelled` — receive orders, and stop when one is cancelled. Add `order.validation_requested` if you pre-validate.
- **Reporting or reconciliation (an OMS, dashboard, analytics):** also `order.created` and `order.paid`, to mirror the order and payment lifecycle.

### The `order.notification` payload

`order.notification` (and `order.validation_requested`) carry the order’s content in `data` — enough to start fulfilling. It uses the same field names and integer-cent amounts as the order resource, but is a smaller object with these keys (as with every payload, ignore keys you don’t recognize):

```json
{
  "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 }
}
```

`order.validation_requested` carries the same `data` plus a `validation_id`. Echo it in your `POST /v1/orders/{orderId}/validation_result` so the result is bound to this attempt; a result for an expired or older attempt is rejected. See [Receive orders](https://docs.maple.inc/developer-api/guides/receive-orders.md).

This is the order’s **content**, not its live state — it has no `status` or `payment`. For the authoritative current state (status, payment, per-item tax, timestamps), fetch [`GET /v1/orders/{orderId}`](https://docs.maple.inc/developer-api/concepts/order-lifecycle.md). The lifecycle events carry only the summary fields listed above.

`scheduled_for` is `null` for ASAP orders. When set (ISO 8601, UTC), the customer chose that pickup/delivery time — the notification still arrives immediately, so prepare the order for `scheduled_for`, not on receipt.

`delivery_address` is `null` for pickup orders and for delivery orders without a stored address. When present, all six keys are included; `notes` contains the delivery instructions and `unit` or `notes` may be `null`.

### Not a status feed

Maple does **not** emit a webhook for every status transition. The transitions you drive yourself (`accept`, `ready`, `complete`) aren’t echoed back, and there’s no event for reaching `IN_DELIVERY` or for payment moving to `refunded` / `voided` / `failed`. Read the order resource for current state — see the [Order lifecycle](https://docs.maple.inc/developer-api/concepts/order-lifecycle.md#webhooks-dont-track-every-transition).

## Booking events

Booking events are delivered to every active app granted the location whose allowed scopes include `bookings:read` and whose subscription matches the event type. They do not require the app to own the POS connection. The location’s bookings rollout flag must be enabled.

| Type                | Change reported                                     |
| ------------------- | --------------------------------------------------- |
| `booking.created`   | Booking created or first observed from its provider |
| `booking.confirmed` | Request confirmed                                   |
| `booking.updated`   | Booking changed                                     |
| `booking.cancelled` | Booking cancelled                                   |
| `booking.declined`  | Request declined                                    |
| `booking.seated`    | Party seated                                        |
| `booking.completed` | Service completed                                   |
| `booking.no_show`   | Party marked no-show                                |

All eight use the same `data` shape. The embedded `booking` matches the GET resource:

```json
{
  "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,
    "experience": null,
    "updated_at": "2026-09-10T18:01:00.000Z"
  }
}
```

The payload is the **latest booking state at first dispatch**, not an immutable snapshot of the transition. `previous_status` is `null`. An event name can therefore describe an earlier change than the embedded booking’s current status. Once recorded for a recipient, retries keep that payload and event identity.

Every original outbox event gets a stable identity per app/environment. Two changes to the same booking remain distinct even when both are dispatched at the same booking version. Dedupe by event ID and reconcile by booking `id`/`version`; do not depend on delivery order. The independent developer consumer does not advance the guest-communications cursor. No floor events are forwarded in this release; waitlist changes have their own [events](#waitlist-events).

A recipient’s enqueue failure does not stop other eligible apps or subscriptions from being enqueued. Retries reuse the same per-subscription event identity. Booking-event publication uses at most three dispatch attempts, each with bounded internal retries. Malformed internal events and exhausted dispatch attempts are retained for operator investigation instead of blocking later events or retrying forever. This is separate from [HTTP webhook delivery retries](https://docs.maple.inc/developer-api/concepts/webhooks.md). Use `GET /v1/bookings?updated_since=…` to reconcile current booking state after a delivery gap.

## Waitlist events

Waitlist events reach the same apps as booking events: every active app granted the location whose allowed scopes include `bookings:read` and whose subscription matches the event type. They are sent for Maple-native locations while the location’s bookings rollout flag is enabled. See [Run the waitlist](https://docs.maple.inc/developer-api/guides/run-the-waitlist.md).

| Type                      | Change reported                                                                  |
| ------------------------- | -------------------------------------------------------------------------------- |
| `waitlist_entry.created`  | A party joined the waitlist                                                      |
| `waitlist_entry.notified` | Maple texted the party that their table is ready                                 |
| `waitlist_entry.seated`   | The party was seated into a walk-in booking                                      |
| `waitlist_entry.removed`  | The party left (`removed_by_guest`) or the venue removed it (`removed_by_venue`) |
| `waitlist_entry.expired`  | A notified party did not respond in time                                         |

All five use the same `data` shape. The embedded `waitlist_entry` matches `GET /v1/waitlist_entries/{entryId}`:

```json
{
  "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 }
  }
}
```

Like booking events, the payload is the **latest entry state at first dispatch**: a `waitlist_entry.created` delivered after the party was seated already shows `seated`. Branch on the event type for what happened, and on `waitlist_entry.status` for where the entry is now. Each change has its own event ID per app and environment; dedupe by event ID and do not depend on delivery order. Delivery attempts, retries, and quarantine of malformed internal events follow the booking-event rules above. Seating also creates a walk-in booking, which is reported by the booking events you subscribe to.

## Location (store) events

These track your access to a location — the [connection lifecycle](https://docs.maple.inc/developer-api/concepts/entities.md#the-connection-lifecycle):

| Type                   | When it fires                                           | `data` (indicative)                                    |
| ---------------------- | ------------------------------------------------------- | ------------------------------------------------------ |
| `store.provisioned`    | A merchant granted your app access to a location        | `location_id`                                          |
| `store.deprovisioned`  | A grant was revoked; you’ve lost access to the location | `location_id`                                          |
| `store.status.changed` | Your connection to a location changed                   | `location_id`, `status` (`connected` / `disconnected`) |

## Menu events

| Type                  | When it fires                        | `data` (indicative) |
| --------------------- | ------------------------------------ | ------------------- |
| `menu.sync.completed` | A menu sync completed for a location | `location_id`       |
| `menu.sync.failed`    | A menu sync failed for a location    | `location_id`       |

See [Publish a menu](https://docs.maple.inc/developer-api/guides/publish-menu.md).

## Test event

`webhook.test` is delivered on demand by `POST /v1/webhook_subscriptions/{id}/test`. Use it to verify your handler before any real traffic. It is never emitted by real activity.
