| # | What you build | Typical effort |
|---|---|---|
| 1 | Receive orders — one webhook endpoint with signature verification | ~1–2 days |
| 2 | Decide orders — accept / deny / status calls back to Maple | ~1–2 days |
Step 1 — Connect a location
Orders route to you only once your app is a location’s connected receiver:409. GET and DELETE on the same path inspect and remove your connection.
Step 2 — Subscribe to webhooks
Register an HTTPS endpoint and the event types you want. The full catalog is atGET /v1/webhook_event_types; for the order loop you’ll typically want these:
mwhsec_…). Store it; it is never shown again. The URL must be public HTTPS — private and internal addresses are rejected.
These are the order events. The catalog also includes
order.created, order.paid, store.provisioned,
store.deprovisioned, store.status.changed, and the menu sync events. Subscribe only to what you act on. See the
full list and payloads in Webhooks.Step 3 — Verify every delivery
Each delivery is a Stripe-style envelope:maple-webhook-id (the event id) and maple-webhook-signature in the form t=<unix_seconds>,v1=<hex_hmac>. The signed string is {timestamp}.{subscription_id}.{notification_url}.{raw_body}. Verify it against the raw, unparsed body:
id — delivery is at-least-once. The full reliability contract, including the retry schedule and the ledger, is in Webhooks.
Step 4 — Read the order
Theorder.notification payload’s data is the order’s content — its IDs, customer, line items, and totals — so you can start fulfilling straight from the webhook without another call. It follows the GET /v1/orders/{orderId} shape, but omits the live status and payment. Fetch the order resource any time for the authoritative current state, including status and payment:
- Money is integer USD cents.
450is $4.50. Never parse it as a float. Every amount intotalsand on line items follows this convention. - Totals are precomputed.
totals.totalis authoritative. You do not re-price anything. - Line items reference your menu by
menu_entity_id, whose value is theexternalIdyou published for that item. Publish a menu first and these line up with your own catalog. (Menu payloads are camelCase; order and webhook payloads are snake_case — see Conventions.) - Modifiers are the directly-selected, first-level options. Deeper nested modifier selections aren’t expanded into the order resource in v1.
- Customer data is minimal. You get a name and the last four digits of the phone number, never the full number.
Step 5 — Decide and report progress
Respond through the decision endpoints as the order moves. None take a body unless noted:| Call | When |
|---|---|
POST /v1/orders/{orderId}/accept | You can fulfill it |
POST /v1/orders/{orderId}/deny | You can’t — optional { "reason": "…" } |
POST /v1/orders/{orderId}/ready | Ready for pickup or courier handoff |
POST /v1/orders/{orderId}/complete | Fulfilled |
POST /v1/orders/{orderId}/cancel | You must cancel after accepting — optional { "reason": "…" } |
POST /v1/orders/{orderId}/status | Granular transition (see below) |
Response
POST /v1/orders/{orderId}/status takes an explicit transition:
ACCEPTED, READY, IN_DELIVERY, FULFILLED, REJECTED, STORE_CANCELLED. For what each status means and the legal transitions between them, see the Order lifecycle.
Decision calls are replay-safe. Repeating one returns
{ "status": "received" } with no double side effects, so
retrying on a network blip is always safe. See Idempotency and replay safety.Optional — pre-validate orders
Pre-validation is opt-in, and you turn it on by subscribing toorder.validation_requested:
- If you don’t subscribe, there’s no validation step. Maple sends
order.notificationdirectly, and your accept/deny is the only gate. - If you subscribe, Maple asks you to confirm each order is fulfillable (item availability, pricing feasibility, POS injectability) before it sends the notification, and waits for your answer. Opting in is therefore a commitment: an order you don’t validate in time is rejected (see below).
order.validation_requested with a result:
valid result lets the order proceed to notification; invalid blocks it before the customer is charged. You have about 5 minutes to respond before a request expires (the resource carries expires_at, ~300 seconds out). If it expires, Maple requests validation once more; if that second request also goes unanswered, the order is rejected without a notification — so only subscribe once your handler reliably answers in time. It’s the same availability check you’d run at accept time, only earlier, so customers don’t pay for something you can’t make.
What “done” looks like
| You build | You get |
|---|---|
| One webhook handler that verifies, dedupes, and routes | Real-time orders pushed with precomputed totals |
| Accept / deny / ready / complete / status calls | Customers see live order progress |
| (Optional) validation responses | Bad orders blocked before the customer is charged |
Next
Publish a menu
Make
menu_entity_id on every line item map to your own catalog.Webhooks in depth
Delivery guarantees, retries, the event ledger, and replay.