This guide explains how the Thanx basket lifecycle works end-to-end and how to diagnose the most common integration problems. It complements the Create & Update Basket endpoint reference with the behavioral context that most integrations need to pass certification on the first attempt.
Who this is for:
- Partners integrating the Loyalty API directly (POS, kiosk, online ordering)
- Anyone troubleshooting missing points, $0 discounts, or unexplained basket behavior
What you’ll learn:
- How a basket moves through its states and what the API does at each transition
- Which fields drive points accrual, and when they must be sent
- The three most common integration mistakes and how to fix them
- How to debug a basket that returned 201 but produced no purchase
The Basket State Machine
A basket is an object partners manage across several API calls. Each call updates the basket’s state, and points are only accrued at a specific transition. For the full state diagram, see the Create & Update Basket endpoint reference.
| State | When to send | What Thanx does | Required? |
|---|
checkout | Any time the user modifies the basket or rewards pre-submission | Validates reward eligibility, returns updated discount amounts | Required |
placed | Order has been submitted; user can no longer modify | Locks the reward; if using a points product, deducts the user’s points balance | Required |
billed | POS charged the card / payment was captured | Creates the purchase and accrues points. Reward is marked used. | Required |
completed | Order handed to the customer | Finalizes the order record | Recommended |
voided | Order canceled between placed and billed | Re-delivers locked reward; points products are not re-credited but reward stays available | Recommended |
refunded | Order canceled after billed | Immediately re-delivers the locked reward, and enqueues a deferred clawback of accrued loyalty points for the associated purchase. Points-product balances are not automatically re-credited. | Recommended |
Points are only created at billed. If your POS never transitions to billed, no purchase is created and no points accrue — even if checkout and placed succeeded.
Three Concepts Every Integration Must Get Right
1. Basket ID is partner-generated
The id field in the basket payload is an identifier your system creates. Thanx does not return a new ID — you reuse the same value across every state transition so the API can correlate the updates to a single basket.
- Generate once when the user starts checkout.
- Reuse for every subsequent request (
placed, billed, completed, etc.).
- Only
checkout requests may omit the ID; every other state requires it.
A common mistake is interpreting the endpoint reference (“Basket ID is not required when submitting a basket with ‘checkout’ state”) to mean the API assigns the ID. It does not. If you generate a new ID for each request, each basket is treated as a separate order and points will not accrue correctly.
2. payments[].amount drives points accrual — not items[] or the grand total
Points are calculated from the sum of payments[].amount values on the billed request. The formula is:
payments[].amount = subtotal - tips - discounts
This means:
- Do not send the order’s grand total (which includes tips and tax).
- Do not send the items total (which doesn’t account for discounts).
- Send the amount the customer actually paid toward loyalty-eligible goods.
If payments is missing or empty on the billed request, no purchase is created. The API will return 201 but nothing lands downstream.
3. items[].price must include modifier prices
When you send items, the price for each item must be the final item price including any modifiers (base price + modifier prices). Sending price: 0 or the base-only price causes item-level discount rewards to calculate a $0 discount.
| Scenario | Correct price |
|---|
| Burger (10)+Extracheese(1.50) | 11.50 |
| Coffee ($4) + no modifiers | 4.00 |
| Combo (15)withdrinkupgrade(2) | 17.00 |
Endpoint URLs: Sandbox vs. Production
The Loyalty API lives on a dedicated subdomain. Using the wrong base URL returns 404s or routes to an unrelated service that will not accept basket requests.
| Environment | Base URL |
|---|
| Sandbox | https://loyalty.thanxsandbox.com/api/ |
| Production | https://loyalty.thanx.com/api/ |
Do not use https://api.thanxsandbox.com/loyalty/* — it’s a different path that is not a supported entry point for basket requests.
See Loyalty API Headers for the full list. Two headers cause the majority of “why are my baskets silently lost” tickets:
Merchant-Key — the merchant hashid (e.g., example-hashid-from-your-rep), not the numeric merchant ID. Sending a numeric ID (or any value that isn’t a valid hashid) returns 401 "The Merchant-Key header is missing or invalid" — no basket or purchase is created. Your Thanx representative provides the correct key during onboarding.
User-Agent — must identify your integration; some auth paths reject generic values.
Sandbox Testing: Expected Latency
Sandbox purchase processing is noticeably slower than production. Delays of several minutes up to tens of minutes between a successful billed request and the purchase appearing in GET /account responses are common based on observed partner testing. Production is near real-time.
Before reporting “points not accruing” in sandbox, wait and re-check after tens of minutes. Most “missing points” sandbox tickets resolve themselves once the worker catches up.
Troubleshooting
Symptom: billed returns 201 but no purchase appears
| Likely cause | How to verify | Fix |
|---|
Wrong Merchant-Key header | Check the header value sent against the one your Thanx representative provided | Use the hashid, not the numeric merchant ID |
Missing payments[] on billed | Inspect the request body — is payments present and non-empty? | Include payments with amount, last4, issuer |
| Sandbox latency | Confirm time since request | Re-check after tens of minutes; sandbox processing is slower than production |
| Wrong base URL | Check outgoing URL | Use loyalty.thanxsandbox.com/api/, not api.thanxsandbox.com/loyalty/ |
Symptom: Item-level reward discount is $0
| Likely cause | How to verify | Fix |
|---|
items[].price does not include modifier prices | Compare sent price against POS receipt | Send base + modifier total |
items[].id mismatches the reward template’s item IDs | Compare against the reward template’s products | Use the exact IDs Thanx issued |
Symptom: Points accrue for the wrong amount
| Likely cause | How to verify | Fix |
|---|
payments[].amount includes tips or tax | Compute subtotal - tips - discounts and compare | Send subtotal - tips - discounts |
payments[].amount reflects the grand total | Same as above | Same |
Symptom: Basket ID keeps changing between calls
Your system is generating a new UUID per request instead of reusing the one from checkout. Generate the ID once when checkout starts, persist it, and send the same value on every state transition.
Before You Certify
Use the Pre-Certification Self-Check checklist for your integration type (POS/Kiosk, Consumer UX, Pay-at-Table) to confirm your basket flow is correct before submitting for certification. The checklist mirrors the scenarios in this guide.
Related Pages