General Requirements
Read first: Overview and Concepts
This page collects everything that is true of every Digital Ordering API call, so the capability guides that follow can stay focused on one flow at a time: how requests travel, how each direction is authenticated, how LoyaltyPlant identifies a sales outlet, the field conventions that apply across operations, and where you build and test against. Field-level truth — schemas, types, enums, per-operation examples — lives in the Digital Ordering API specification; this page links to it and never restates schemas.
Read this twice. Digital Ordering inverts the usual mental model. You (the vendor / POS integration) host the five
/rest/...endpoints; LoyaltyPlant is the HTTP client that calls them. The only call that runs the other way is the order-status webhook you POST to LoyaltyPlant. The timeouts below are therefore your response budget, and the requestAuthorizationTokenis something you validate, not something you send.
1. Transport and connectivity
You host a base URL — written {vendorBaseUrl} throughout this set — and LoyaltyPlant appends /rest/... to it to form each endpoint, e.g. {vendorBaseUrl}/rest/order. The base URL must be reachable from LoyaltyPlant over TLS; its real value is provisioned during onboarding (§6).
| Property | Value |
|---|---|
| Protocol | HTTPS |
| Method | GET for /rest/healthcheck, /rest/menu, /rest/menu/revision; POST for /rest/order, /rest/orders |
| URL shape | {vendorBaseUrl}/rest/{endpoint} |
| Body media type | application/json (LoyaltyPlant sends and accepts application/json;charset=UTF-8 on the order and healthcheck calls) |
| Character encoding | UTF-8 |
| Redirects | LoyaltyPlant follows HTTP redirects (a "lax" redirect policy) |
1.1 Timeouts — your response budget
LoyaltyPlant applies fixed client-side timeouts when it calls you. Because LoyaltyPlant is the client, these are the windows your endpoints must respond within — exceed them and LoyaltyPlant treats the call as failed (an unavailable outlet on healthcheck, a discarded menu, an order that was not injected). The connect and read timeouts differ by operation family:
| Operation | Connect | Read |
|---|---|---|
POST /rest/order, POST /rest/orders, GET /rest/healthcheck | 5 s | 60 s |
GET /rest/menu, GET /rest/menu/revision | 30 s | 180 s |
Note: The menu read window is the most generous (180 s) because a full menu can be large; even so, return a revision quickly on
GET /rest/menu/revisionso LoyaltyPlant can skip the heavy menu pull when nothing changed (see the Menu Sync Guide). The 60 s order/healthcheck window is comfortable for a single POS write — do not block it on slow downstream calls.
2. Authentication — three directions
This is the keystone section. The Digital Ordering API has three distinct authentication directions, and the most common integration mistake is conflating them. All three hash the shared key with SHA-256 — directions (a) and (c) hash the apiKey, direction (b) hashes the apiKeyResponse. Read the table, then the per-direction detail.
| # | Direction | On which calls | AuthorizationToken value | What enforces it |
|---|---|---|---|---|
| (a) | LoyaltyPlant → you, on the request | /menu, /menu/revision, /healthcheck, /order, /orders | SHA256-hex(apiKey), lowercase hex | You validate it; reject a mismatch |
| (b) | You → LoyaltyPlant, on the response | /order, /orders, /menu, /menu/revision, /healthcheck | SHA256-hex(apiKeyResponse), lowercase hex | LoyaltyPlant verifies it; missing/wrong → whole response discarded |
| (c) | You → LoyaltyPlant, on the webhook | POST {lpBaseUrl}/digitalordering/pos/v2/integrated | SHA256-hex(apiKey), lowercase hex | LoyaltyPlant verifies it; mismatch → VALIDATION_ERROR |
Two shared secrets are in play. The apiKey is used on directions (a) and (c); a second, distinct key, the apiKeyResponse, is used only on direction (b). Both are assigned per outlet during onboarding (§3, §6).
2.1 (a) The request token LoyaltyPlant sends you
On every call LoyaltyPlant makes to your endpoints, it sends two headers:
SalesOutletId: 4198
AuthorizationToken: a3f5...e1 (64 lowercase hex chars = SHA256-hex of the apiKey)
The AuthorizationToken is the lowercase SHA-256 hex of the shared apiKey LoyaltyPlant holds for that outlet. Compute the same hash from the key you hold for the SalesOutletId and compare; reject the call if it does not match. LoyaltyPlant sends this on /menu, /menu/revision, /healthcheck, /order, and /orders.
authorizationToken = lowercase_hex( SHA256( apiKey ) )
2.2 (b) The response token you must echo back
On every LoyaltyPlant→vendor response — /order, /orders, /menu, /menu/revision, and /healthcheck — your HTTP response MUST carry an AuthorizationToken header equal to the lowercase SHA-256 hex of the apiKeyResponse. LoyaltyPlant verifies this header and, if it is missing or does not match, discards the entire response and treats the operation as failed — the menu is not imported, the order is treated as not created, the poll yields nothing.
AuthorizationToken: 9b74...c0 (64 lowercase hex chars = SHA256-hex of the apiKeyResponse)
responseAuthorizationToken = lowercase_hex( SHA256( apiKeyResponse ) )
Note: The
apiKeyResponseis a separate key from the requestapiKeyand may be set to a different value. In practice, most integrations configure the two to the same value — that is allowed, but do not rely on it: treat the response token asSHA256-hex(apiKeyResponse)and the request token asSHA256-hex(apiKey), and let onboarding decide whether the two keys coincide.
2.3 (c) The webhook token you send
The order-status webhook is the one call that travels from you to LoyaltyPlant. It carries two headers: SalesOutletId (numeric) and an AuthorizationToken. The webhook AuthorizationToken is SHA256-hex(apiKey) — the same hashed value as the request token in direction (a). LoyaltyPlant compares it case-insensitively.
SalesOutletId: 4198
AuthorizationToken: <SHA256-hex(apiKey), lowercase hex>
Note: The webhook uses the same
SHA256-hex(apiKey)token as the LoyaltyPlant→vendor request (direction (a)) — all three auth directions are on the hashed scheme, so there is no separate raw-key rule to special-case. Full webhook header rules and the validation order are in the Status Webhook Guide.
2.4 Validating a request — the rhythm in pseudocode
The same skeleton applies to all of your /rest/... handlers:
on incoming LP -> vendor request:
salesOutletId = header "SalesOutletId" # numeric
settings = lookup_outlet(salesOutletId) # apiKey + apiKeyResponse
expected = lowercase_hex(SHA256(settings.apiKey))
if header "AuthorizationToken" != expected: # case-insensitive
reject the request
... build the response body ...
set response header "AuthorizationToken" =
lowercase_hex(SHA256(settings.apiKeyResponse))
return response
when you push a status update to LoyaltyPlant (the webhook):
set header "SalesOutletId" = <numeric outlet id>
set header "AuthorizationToken" = lowercase_hex(SHA256(settings.apiKey)) # same hashed token as the request (direction a)
POST {lpBaseUrl}/digitalordering/pos/v2/integrated
3. Outlet identity
Every LoyaltyPlant→vendor call, and every webhook you send, carries a numeric SalesOutletId header identifying the physical sales outlet the call targets. Resolve it on your side to the correct sales outlet, menu, prices, and the apiKey / apiKeyResponse pair for that outlet.
- One outlet, one menu, one configuration. Because each request is scoped by
SalesOutletId, the same brand can serve different menus, prices, and availability at different outlets — yourGET /rest/menuresponse andPOST /rest/orderhandling must vary per outlet. SalesOutletIdmust be numeric on the webhook. LoyaltyPlant parses the webhook'sSalesOutletIdheader as an integer; a non-numeric value is rejected withVALIDATION_ERRORbefore any status is applied.- Configurability is a certification item.
SalesOutletId(and the two keys) must be configurable per outlet on your side, never hard-coded — see the Integration Checklist. - LoyaltyPlant provides the
SalesOutletIdvalues. You do not invent them. The test outlet's id comes in your onboarding package (§6); the productionSalesOutletIds, mapped to your physical outlets, are provided by LoyaltyPlant when a mutual client is provisioned for launch (see Overview §4.5).
4. Conventions
These rules hold across all operations. They are what a reader most often gets wrong from the schemas alone. The spec (do-api-v2-spec.yaml) is authoritative for exact field types and required-ness.
4.1 Field types and null handling
JSON fields use standard types: strings, integers, and decimal number. LoyaltyPlant omits null fields from the wire rather than sending explicit nulls, and empty menu sub-objects are omitted on serialization; plan for fields and sub-objects to be simply absent rather than present-and-null. (The top-level menu arrays — categories, modifierGroups, modifiers, items — are an exception: they may still appear as empty arrays [] rather than being absent when nothing is present.)
4.2 The menu revision (epoch timestamp)
The menu revision is an epoch timestamp (int64, seconds), returned identically by GET /rest/menu (inside menu.revision) and GET /rest/menu/revision. LoyaltyPlant uses it to decide whether to re-import: it re-pulls the full menu when the revision changed, when it has no cached value, or when the returned revision is lower than its cache. The two endpoints must agree on the value. Full behavior is in the Menu Sync Guide.
4.3 Money and decimals
Monetary fields (price, value, subtotal, tax, total, deliveryFee, tips, discount) are JSON number (decimal). On a createOrder request the charge breakdown's tax, subtotal, and total are always sent; reward items in presents[] must be charged 0 (they never increase the total the customer pays).
Note: The protocol does not declare a currency, decimal scale, or rounding rule on the wire — monetary values are plain decimals, and the outlet's currency is configured on the LoyaltyPlant side. Agree the decimal precision and rounding to use with LoyaltyPlant at onboarding and confirm it at certification.
4.4 Timestamps
Time-of-fulfilment timestamps (deliveryAt.datetime) are ISO 8601 strings, sent only when deliveryAt.type = CUSTOM; for ASAP no datetime is sent. The menu revision is the one timestamp that is an epoch integer, not ISO (§4.2).
4.5 Multilingual text
Menu title and description are multilingual objects keyed by ISO 639-1 language code (e.g. {"en": "Cheeseburger", "es": "Hamburguesa con queso"}), not plain strings. At least one language is expected on a required title. Provide every language the outlet serves.
4.6 Order types
Order.type is one of PICKUP (collect at the counter), DELIVERY (courier delivery — address with coordinates and addressString1 is required), EAT_IN (dine-in, optionally with additionalInfo.tableNumber), and DRIVE_THROUGH (drive-through pickup). The enum also carries CATERING (defined in the enum but with no current production usage — verify against your POS model before relying on it). For dine-in at a table use EAT_IN plus tableNumber. Details and examples per type are in the Order Injection Guide.
5. Versioning
This documentation set and the Digital Ordering API specification describe the current contract for new integrations. There is no version header or content negotiation: the endpoints are always /rest/menu, /rest/order, and so on.
6. Environments and test tooling
The base URL you host ({vendorBaseUrl}) and the LoyaltyPlant webhook base URL ({lpBaseUrl}) are real, per-environment values provisioned during onboarding — examples in this set and the servers entry in the spec are illustrative placeholders, not endpoints to hardcode. The deployed webhook path is {lpBaseUrl}/digitalordering/pos/v2/integrated.
Note: Your test sales outlet, its
SalesOutletId, theapiKey/apiKeyResponsepair, and the test{lpBaseUrl}come with your onboarding package. Confirm the exact test host and credentials with LoyaltyPlant at onboarding.
7. Glossary
Terms used throughout this documentation set. Field-level definitions live in the spec; this table gives the meaning.
| Term | Meaning |
|---|---|
| SalesOutletId | Numeric id of one physical sales outlet, sent as a header on every LoyaltyPlant→vendor call and on the webhook; resolve it to the sales outlet's menu, configuration, and keys. |
| sales outlet | A single physical store, identified by its SalesOutletId. (If you also integrate the In-Store Loyalty API, note that it calls the same physical store an establishmentId.) |
| apiKey | The shared outlet secret used to authenticate requests: hashed (SHA256-hex) on both LoyaltyPlant→vendor calls and the webhook (§2.3). |
| apiKeyResponse | The response-side outlet secret (which may differ from apiKey); its SHA256-hex is the AuthorizationToken your responses must carry on /order, /orders, /menu, /menu/revision, and /healthcheck (§2.2). |
| revision | The menu version, an epoch-timestamp int64 returned by both menu endpoints; LoyaltyPlant compares it to its cache to decide whether to re-import (§4.2). |
| refId | A response-scoped reference key, unique within a single /rest/menu response, by which menu parents reference children (an item's category, an embedded group's basedOn). May be reused across separate responses. |
| posId | The POS's own id for an order, returned from createOrder; LoyaltyPlant stores it and uses it on getOrders and the webhook. |
| queueNumber | The customer-facing queue / pickup number the POS assigns and echoes on reads and the webhook (declared as string; numeric accepted). |
| webhook | The single vendor→LoyaltyPlant push: POST {lpBaseUrl}/digitalordering/pos/v2/integrated, reporting an order's status change (§2.3, Status Webhook Guide). |
Next: Ordering Flows →