Integrate Zelty (POS auto-mint and burn for Loyalty)
Wire a Zelty restaurant to a Keyban Loyalty application so paid orders
automatically credit loyalty points to the customer, and so cashiers can
redeem points back from the POS at the next visit. The mechanics rely
on a single Zelty webhook (order.ended) inbound to Keyban, plus a
public burn endpoint Keyban exposes for the POS to call.
How the integration runs
Auto-mint after a paid order
- Customerpays at the counter
- Zelty POScloses the order
- Zelty backendemits webhooks
- Keybanloyalty + indexer
- 1pays for the order
- 2order.status = 255 (closed)
- 3POST /loyalty/zelty/webhooks/order.ended
- 4verify HMAC SHA-256 signature
- 5match restaurant + Zelty customer → mint
The mint only happens when all of the following hold; otherwise the webhook is logged and skipped:
- Order
statusequals255(Zelty's « closed » terminal state). - The order carries a
customer(the cashier scanned the loyalty card or registered the customer at checkout). - The HMAC signature on the webhook header matches Keyban's shared secret (Keyban rejects the call otherwise).
- The Zelty restaurant ID is bound to a Keyban application.
Burn from the POS at redemption time
- Customerredeems points
- Zelty POSinitiates the burn
- Keybanloyalty + chain
- Ledgersettles the burn
- 1asks to redeem N points
- 2POST /loyalty/zelty/burn/:restaurantId/:customerId
- 3verify order belongs to the customer
- 4burn N points
- 5updated balance
The burn endpoint is public (no API key required) but every call re-checks the order against Zelty before touching the balance — a POS cannot burn points on someone else's account.
One-time setup
Prerequisites
- Access to your Zelty back-office: https://bo.zelty.fr.
- Admin rights on the Keyban Admin Console
with
zelty/settings: ['edit']permission. - A Loyalty application already provisioned (network and reward tiers in place).
Step 1 — Generate a Zelty API key
Option A — via the marketplace
If Keyban appears in your Zelty marketplace:
- Open https://bo.zelty.fr/marketplace.
- Find Keyban and open its integration page.
- Generate or retrieve your API key.
Option B — via Zelty's manual form
If Keyban is not yet on your marketplace:
- Submit the request at https://zelty.fr/forms/api.
- Per Zelty's documented turn-around, keys are issued Tuesday and Thursday end of day.
Step 2 — Create the transaction method and capture its ID
Keyban needs a Zelty transaction method it can attribute its on-chain settlements to. The display name does not matter — Keyban identifies the method by its Zelty ID, not its name.
- Open https://bo.zelty.fr/transaction-methods.
- Go to Configurations → Payment Methods, click Add.
- Pick a name your cashiers will recognise (« Keyban » is a sensible
default but anything works) and a special type (
Noneis fine). - Save the method, then open it again — the Transaction method ID shows up in the URL or the detail panel. Copy it; you'll paste it in Step 3.
- Optional: define the method at the brand level if you want it shared across multiple restaurants, or at the restaurant level for a single location.
Step 3 — Wire the integration in the Keyban Admin Console
- Sign in to the Keyban Admin Console and open Organization → Integrations → Zelty.
- Paste the API key from Step 1.
- Paste the Transaction method ID from Step 2.
- Save.
On save, Keyban automatically registers an order.ended webhook
against your Zelty account (target:
{keyban-host}/loyalty/zelty/webhooks/order.ended) and stores the
shared HMAC secret. From there, every closed order at the bound
restaurant flows through the Auto-mint sequence above.
To bind a specific restaurant to a Loyalty application, open the application in the Admin Console and pick the Zelty restaurant from the Point of sale picker. The restaurant ↔ application link is exclusive — see Constraints below.
Constraints to know about
- One restaurant ↔ one application. A given Zelty restaurant ID can be bound to a single Keyban application, and a given Keyban application accepts at most one Zelty restaurant. Switching the binding is possible from the Admin Console, but every prior accrual on the old application stays where it was minted.
- The customer must exist in Zelty. Auto-mint relies on the
order.customerfield. A walk-in checkout without a registered customer logs « no customer associated with order… skipping » and no points are minted. - Only
status === 255triggers a mint. Partial closures, cancels, and refunds carry different statuses and are deliberately ignored. - Webhook authentication is HMAC SHA-256, not an API key. Keyban rejects any call whose header signature does not match the secret installed in Step 3.
Troubleshooting
| Symptom | First check |
|---|---|
| « Webhook received but nothing minted » | Order status (must be 255) and order.customer (must be set) |
« 401/403 on /loyalty/zelty/webhooks/... » | HMAC mismatch — re-save the integration in the Admin Console to rotate the shared secret |
| « API key looked good but Step 3 fails » | Marketplace key vs. manual-form key delay — wait for the next Tuesday/Thursday batch if the form route was used |
| « Burn returns 400 from the POS » | The order ID, the customer ID and the restaurant ID must all match — Keyban re-verifies the triplet against Zelty before debiting |
Next steps
- Surface the live points balance in the customer's wallet pass: Connect Apple and Google Wallet passes.
- Manage tiers and visibility from the Admin Console; for automation scenarios outside Zelty, see Automate Loyalty via API.
- Browse the full Loyalty surface:
/backend-openapi#tag/loyalty.