Ordering Flows
Read first: Overview and Concepts · General Requirements
This document describes how Digital Ordering works end to end with the Digital Ordering API contract: how the menu reaches the customer, how an order is placed and reaches your point-of-sale (POS), and how its status flows back to the customer's app. It is written at the business level — what the customer, LoyaltyPlant, and your POS each do — so that product managers and integration managers can follow it as easily as engineers. Field-level detail (schemas, headers, status codes, error codes) lives in the Digital Ordering API specification and in the capability guides that follow this document.
1. How Digital Ordering works
Before anything else, read this paragraph twice, because it inverts the usual mental model of an API. In Digital Ordering, your service implements the API and LoyaltyPlant calls it. LoyaltyPlant is the HTTP client; the endpoints described in this contract — getting the menu, checking availability, creating an order — live on your server, and LoyaltyPlant sends requests to them. This is the opposite of a normal "we host an API, you call us" integration, and it is the single most common source of confusion when teams first start.
There is exactly one exception, and it runs the other way: the order-status webhook. When you have status updates to report, your POS calls LoyaltyPlant — see §4.
The diagram shows three directions of traffic:
- Customer → LoyaltyPlant. The customer browses the menu and places the order entirely inside the LoyaltyPlant app. They never touch your system directly.
- LoyaltyPlant → your POS (solid arrows). LoyaltyPlant pulls your menu, checks your availability, and injects orders by calling the REST endpoints you host. These are the bulk of the contract.
- Your POS → LoyaltyPlant (dashed arrow). Status changes flow back through the webhook so the customer can watch the order progress.
Everything that follows is a detailed view of these three directions. The endpoints map to the flows like this:
| Endpoint (you host) | Direction | Business meaning | Flow |
|---|---|---|---|
GET /rest/menu, GET /rest/menu/revision | LP → you | "What can the customer order, and has it changed?" | Menu sync (§2) |
GET /rest/healthcheck | LP → you | "Are you able to take an order right now?" | Availability (§5) |
POST /rest/order | LP → you | "Here is a confirmed order — start preparing it." | Placing an order (§3) |
POST /rest/orders | LP → you | "What is the current state of these orders?" | Status — poll path (§4) |
POST /pos/v2/integrated (LP hosts this one) | you → LP | "This order's status just changed." | Status — push path (§4) |
2. Menu synchronization
Before a customer can order anything, LoyaltyPlant needs an up-to-date copy of what you sell. It keeps that copy fresh by pulling your menu on a schedule — you never push the menu to LoyaltyPlant. On each sync cycle, LoyaltyPlant fetches the full menu from your GET /rest/menu endpoint, converts it into its internal format, and stores it so the customer-facing app always sees current items, prices, and availability.
To avoid re-downloading a large menu that has not changed, LoyaltyPlant can first call the lightweight GET /rest/menu/revision endpoint, which returns only a single revision number. If that number matches the one from the last successful sync, the full menu load is skipped; if it differs (or there is no cached value yet), the full GET /rest/menu proceeds as usual.
The key point of the diagram: the customer always sees LoyaltyPlant's stored copy of your menu, not your live system — so menu freshness is bounded by the sync interval, not by the moment the customer opens the app.
What this means in business terms:
- Freshness has a lag. A price change or a new item appears to customers only after the next successful sync. The sync interval is configured per outlet on the LoyaltyPlant side.
- Each outlet has its own menu. Every menu request carries the salesOutletId so LoyaltyPlant maps the response to the correct outlet; the same brand can serve different menus and prices at different outlets.
- The stop-list controls what the customer can buy. An item you omit from the next
GET /rest/menuresponse is automatically marked unavailable and hidden from customers — there is no separate "delete" call. This is how a sold-out item disappears from the app. - The revision check is an optimization, not a gate. If the revision pre-check fails for any reason, LoyaltyPlant falls back to a full menu load — a failure in the optimization path never blocks menu synchronization.
Note: The revision value is an epoch timestamp; it is the same number your
GET /rest/menuresponse carries in itsrevisionfield. If the value LoyaltyPlant receives is lower than the cached one, it treats the menu as changed and does a full load to stay consistent.
The exact shape of the menu payload — categories, items, modifier groups, free modifiers, taxes by order type — is defined in the Digital Ordering API specification and walked through in the Menu Sync Guide.
3. Placing an order
When a customer checks out in the app, the order does not exist in your POS yet — LoyaltyPlant has only taken the order and, where applicable, the payment. To turn it into a real kitchen ticket, LoyaltyPlant injects the order into your POS by calling POST /rest/order. The body carries the full order: the customer's contact details, the chosen items and modifiers, any gifts, the delivery or pickup time, the payment summary, and the order type. Your POS creates the order and answers with the POS order id (posId) and a short queueNumber the customer can show at the counter.
Crucially, LoyaltyPlant always checks your availability first — it calls GET /rest/healthcheck immediately before POST /rest/order, and only injects the order if every service you report is healthy (see §5).
The key point of the diagram: the healthcheck always runs first, so your POS only ever receives orders it has just told LoyaltyPlant it can handle.
Through the customer's eyes, the order type decides what the experience looks like, and your POS must honor it:
- Pickup — the customer comes to collect the order and shows the queue number; no address is involved.
- Delivery — the order carries a delivery address and coordinates, and may include a delivery fee; the customer expects the order brought to them. For this type the address block is required.
- Eat-in — the customer dines in, optionally tied to a table number passed in the order.
- Catering — a large, typically pre-arranged order. This value is code-backed but is not part of the publicly documented order-type set; confirm with LoyaltyPlant whether your integration will receive it before relying on it.
- Drive-through — the customer collects from a drive-through lane; no address, like pickup.
Two timing and pricing rules shape what your POS does with the injected order:
- Timing. Every order says when it is wanted:
ASAPfor as-soon-as-possible, orCUSTOMwith a specific date-time in the outlet's timezone. Your POS schedules preparation accordingly. - Gifts cost nothing. Reward items arrive in a separate
presentsblock; your POS must add them to the order and set their price to zero — they never increase the total the customer pays.
Once your POS accepts the order, LoyaltyPlant marks it as confirmed and the customer is told their order is on its way to the kitchen. From here the story is about keeping the customer informed — §4.
4. Order status back to the customer
Once the order is in your POS, the customer watches it progress in the app — received, cooking, ready for pickup, being delivered, completed. Keeping that display current is the one place where your POS reaches out to LoyaltyPlant, and there are two ways to do it. You can use either or both.
- Push (webhook). As soon as an order's status changes, your POS POSTs it to LoyaltyPlant at
POST /pos/v2/integrated. The full deployed URL includes a context prefix —{lpBaseUrl}/digitalordering/pos/v2/integrated(LoyaltyPlant gives you the exact base URL during onboarding; the exact path and headers are in the Status Webhook Guide). This is the only call that runs from your side to LoyaltyPlant, and it gives the fastest customer-facing updates. - Poll. Alternatively, LoyaltyPlant periodically asks your POS for the state of orders by calling
POST /rest/orderswith a list of POS order ids; your POS answers with the current status and contents of each.
The key point of the diagram: whichever path you choose, the customer sees the same thing — a live status in the app. Both paths report the same set of nine statuses: PROCESSING, CONFIRMED_WAITING_FOR_COOKING, COOKING, AWAITING_PICKUP, AWAITING_DELIVERY, BEING_DELIVERED, and the terminal PROCESSED, CANCELED, or FAILED.
When an order ends in CANCELED or FAILED, your POS includes a reason — internal information that is not shown to the customer but helps LoyaltyPlant and you diagnose the case. Once the order is completed and closed, the loyalty side of the transaction settles too — LoyaltyPlant grants the customer their points for the order, and reverses them if the order is later canceled or refunded.
Warning: There are three distinct authentication directions — do not conflate them. The exact header rules for each live in General Requirements and the Status Webhook Guide; the summary is:
- LP → you, on the request (menu, menu/revision, healthcheck, order, orders). LoyaltyPlant sends an
AuthorizationTokenheader that is the SHA-256 hex of the shared API key (SHA256-hex(apiKey)); your POS validates it and rejects a mismatch.- You → LP, on the response (
/order,/orders,/menu,/menu/revision, and/healthcheck). Your HTTP response must carry anAuthorizationTokenresponse header equal toSHA256-hex(apiKeyResponse)— the response key (which may differ from the request key). LoyaltyPlant verifies it and discards the entire response (treating the operation as failed) if it is missing or wrong.- You → LP, on the status webhook. The webhook you POST to LoyaltyPlant carries an
AuthorizationTokenequal toSHA256-hex(apiKey)— the same hashed token as the request direction.
5. Availability
LoyaltyPlant will not send an order to a POS it cannot reach. Before every order injection, it calls your GET /rest/healthcheck endpoint, where you report the live state of each service that matters for accepting orders — for example your POS and your database — as UP or DOWN. If any service reports DOWN, LoyaltyPlant treats the sales outlet as unavailable and does not create the order.
The key point of the diagram: the healthcheck is a gate, not a formality — your honest UP/DOWN answer decides whether the order reaches you at all.
What this means for you:
- Report what actually matters. List the real services whose failure would break order acceptance, and return each one's current state. A blanket
UPwhen your kitchen system is actually down means LoyaltyPlant will inject orders you cannot fulfill. - No response is treated as down. If the healthcheck times out, errors, or cannot be parsed, LoyaltyPlant treats the sales outlet as unavailable — the availability check is fail-safe, so a broken healthcheck stops orders rather than letting them through blindly. The verification mechanism is expected to account for the absence of an internet connection and other failures.
Note: Like the
/order,/orders,/menuand/menu/revisioncalls, the healthcheck response must echo the responseAuthorizationTokenback (auth direction 2 in the §4 warning) —SHA256-hex(apiKeyResponse). The full header rules are in General Requirements.
6. What happens when things go wrong
Because the contract spans two systems and a customer who is watching in real time, the failure paths matter as much as the happy path. At the business level:
- The order cannot be injected. If the healthcheck shows a service down, or
POST /rest/orderfails or returns no usable response, LoyaltyPlant treats the order as not injected: the customer does not get a confirmed order, and the situation surfaces immediately rather than producing a silent lost ticket. LoyaltyPlant does not give up after a single failure — whenPOST /rest/orderreturns anything other than a usable200, it retries the injection up to 3 times in total (the initial call plus 2 retries, ~500 ms apart) before treating the order as not injected. Note that LoyaltyPlant cannot distinguish a true failure from a timeout that arrives after your POS has already written the order, so it makes no at-most-once guarantee in that edge case — and because of the retries, the same order may reach your POS more than once. To stay safe against duplicates, key creation on LoyaltyPlant'sorder.idand treat a repeated injection of the same id idempotently. - A status update is rejected. When your POS posts the status webhook, LoyaltyPlant replies with whether it was
acceptedand, if not, a list of errors — for example the body could not be read, the JSON was malformed, validation failed, or an internal error occurred. A rejected webhook means the customer's app did not get that update, so your POS should retry or fall back to letting LoyaltyPlant poll. - A status update never arrives. If the push path misses an update, the poll path is the safety net — LoyaltyPlant's periodic
POST /rest/orderswill pick up the current state on the next cycle. Supporting the poll path means a dropped webhook degrades the customer experience instead of breaking it. - The order is canceled or refunded. A terminal
CANCELEDorFAILEDstatus (with its internalreason) tells LoyaltyPlant the order will not be fulfilled; LoyaltyPlant then returns any loyalty points and rewards tied to the order to the customer.
The exact response shapes, HTTP status codes, and error codes for each of these cases are defined in the Digital Ordering API specification; the recommended handling for each is in the capability and webhook guides.
7. Where it runs — integration shapes
This contract describes a wire protocol, not a deployment. There are two equally valid ways to build the service that hosts these /rest/* endpoints, and both speak the exact same protocol LoyaltyPlant calls.
- Embedded. You add the Digital Ordering API endpoints directly to your POS or back-office platform — LoyaltyPlant talks to your system natively. This suits a POS vendor integrating loyalty ordering into its own product.
- Wrapper / adapter service. You stand up a thin service that implements the Digital Ordering API endpoints and translates them to whatever back end actually holds the menu and orders — a commerce platform, an aggregator, or another POS API. The aggregator-style adapter pattern (the same one used to put an ordering platform's or a delivery-aggregator's catalogue and orders behind a standard storefront API) fits here: LoyaltyPlant calls the standard Digital Ordering endpoints, and your adapter does the mapping behind them.
Note: Whichever shape you pick, LoyaltyPlant sees only the Digital Ordering API contract — the base URL you host (
{vendorBaseUrl}), the/rest/*endpoints under it, and the status webhook you send back. How you fulfill those calls internally is entirely your choice. The capability guides that follow show how to implement each endpoint step by step.
Next: Menu Sync Guide →