The event envelope
Every delivery has the same Stripe-style envelope. Thedata payload is event-specific:
string
Unique event id. Dedupe on this — delivery is at-least-once.
string
The event type. Switch on it to route the event.
number
Unix seconds when the event was created.
object
Event-specific payload.
order.notification carries the full order content; the lifecycle events (order.created, order.paid, order.cancelled) carry summary fields. See Webhook events for each payload, and fetch GET /v1/orders/{orderId} for authoritative current state.Event types
Subscribe only to what you act on. The live catalog is always atGET /v1/webhook_event_types:
webhook.test is also delivered on demand by the test endpoint, so you can verify a handler before any real traffic.
These are discrete events, not a status feed. Maple doesn’t send a webhook for every order status change — the
transitions you drive yourself (
accept, ready, complete) aren’t echoed back, and there’s no per-transition
event. For an order’s current state, read GET /v1/orders/{orderId}. See the Order
lifecycle.Subscribing
Response
notification_url must be public HTTPS — private and internal addresses are rejected. Manage subscriptions with GET, PATCH (update the URL, event types, or enabled/disabled status), and DELETE on /v1/webhook_subscriptions/{id}. Updating a subscription leaves its signing secret unchanged.
Multiple subscriptions
You can register more than one subscription. Every enabled subscription whoseevent_types include a fired event gets its own signed delivery, each with its own signing secret — so overlapping event_types across subscriptions are allowed. This lets you fan out by destination: for example, point order events at your fulfillment service and menu.sync.* events at a separate back-office endpoint. Replay follows the same routing and skips any subscription that already received the event.
Verifying the signature
Each delivery carries two headers:maple-webhook-id— the event id.maple-webhook-signature—t=<unix_seconds>,v1=<hex_hmac>.
Delivery guarantees
- At-least-once. The same event can arrive more than once. Dedupe on the envelope
idand make your handler idempotent. - Respond fast. Return a
2xxquickly, then do slower work asynchronously. Any non-2xx(or a timeout) counts as a failed delivery. - Order is not guaranteed. Don’t assume events arrive in the order they occurred; reconcile against the order resource when sequence matters.
Retries and auto-disable
Each event is delivered with up to 9 attempts — the first immediately, then with increasing backoff. The early attempts fit inside the six-minute POS decision window, while the long tail supports reconciliation after a real outage:
A non-
2xx response (or a timeout — the per-attempt limit is 15 seconds) fails that attempt. An event that fails all 9 attempts counts as one fully-failed delivery. After 5 consecutive fully-failed deliveries — five separate events that each exhausted every attempt — the subscription is automatically disabled; a single successful delivery resets the counter to zero. Re-enable it with a PATCH setting status back to enabled once your endpoint is healthy, then replay anything you missed.
The event ledger
Every event is recorded, so a missed or failed delivery is recoverable.GET /v1/webhook_events— your recent events, most recent first (up to 50). Useful for reconciliation and debugging.GET /v1/webhook_events/{eventId}— a single event, including its fulldatapayload.POST /v1/webhook_events/{eventId}/replay— re-deliver the event to currently-matching subscriptions. Idempotent: subscriptions that already received it are skipped, so replaying is safe.
since as your last successful checkpoint and drain forward: request GET /v1/orders?since=<checkpoint>&limit=100, then repeat with starting_after=<last-order-id> while has_more is true. With since, results are oldest first, so this reaches the entire backlog without skipping orders that share a timestamp. Fetch the authoritative order resource, then call POST /v1/orders/{orderId}/resend only for a live order that missed its webhook. Resend creates a fresh signed order.notification with a new event identity and never changes order state. Terminal orders (REJECTED, CUSTOMER_CANCELLED, or STORE_CANCELLED) cannot be resent or revived; reconcile them with GET /v1/orders?since=... and GET /v1/orders/{orderId}.
Testing your handler
webhook.test event and returns whether it was delivered and the HTTP status your endpoint returned:
2xx.
Checklist
Verify the HMAC signature on every delivery against the raw body, and reject stale timestamps.
Dedupe on the envelope
id; make handlers idempotent.Return
2xx fast; process asynchronously.Subscribe only to the event types you handle.
Monitor for auto-disabled subscriptions and replay from the ledger after an outage.