Skip to content

Going live

Sandbox (prerelease)Production
Base URLhttps://api.prerelease.dillerapp.comhttps://api.diller.app
Token endpoint…/connect/token…/connect/token
CredentialsSandbox onlyProduction only
DataDisposableReal customers
SMSMay be disabledReal messages to real phones

Credentials are not interchangeable. A sandbox client id will not authenticate against production, and vice versa.

Put the base URL in configuration, never in code. The only difference between your sandbox and production build should be four values: base URL, client id, client secret, store id.

DILLER_BASE_URL=https://api.prerelease.dillerapp.com
DILLER_CLIENT_ID=...
DILLER_CLIENT_SECRET=...
DILLER_STORE_ID=...

The store id differs per environment too. Read it from GET /api/v2.0/stores once for each environment and pin it in configuration.


Production credentials belong to the merchant. They find them in their Diller account:

Settings → Our Details → API Details

That page shows the token endpoint, the flow, the scopes, the client id and the client secret. Ask the merchant to send you both over something safer than email — a password manager share, or your usual secrets channel.

Sandbox credentials come from us. Ask for a prerelease store when you start the integration, not the week you plan to launch.


  • Base URL, client id, secret and store id all come from configuration
  • Token is cached and reused, not fetched per request
  • 401 triggers exactly one refresh-and-retry
  • Secret is in a secrets manager, not the repository
  • Nothing Diller-related is reachable from client-side JavaScript
  • Store id is used verbatim, with no case transformation
  • Search before create, every time
  • Empty-array miss handled, not just the HTTP status
  • 400 “already registered” recovers by searching and updating
  • consent.gdpr_accepted comes from a real ticked checkbox
  • receive_sms and receive_email sent on every create and update, each from its own unticked checkbox
  • Merchant’s terms shown, privacy_policy_url linked
  • PUT built from a fresh read — address, departments, segments (as segment_id) and both marketing flags
  • diller_member_id stored on your customer record
  • No silent enrolment on login
  • Segment form rendered from GET /segments
  • Phone changes go through reset-phone, not PUT
  • origin.channel is "OnlineStore" on every call that takes an origin
  • external_id is deterministic — ORDER-{id}, no timestamps
  • origin.department_id and origin.system_id fixed, so a retry is caught as a duplicate
  • is_ok checked on every transaction response
  • created_at carries an explicit UTC offset, and seconds
  • total matches the sum of payment_details
  • Line total_price = quantity x unit_price - discount
  • Cancel and refund paths implemented and tested
  • Transaction posted after payment capture, not before
  • Validate on apply and again before payment
  • Reserve immediately before payment
  • cancel-reservation on payment failure
  • Reservations from abandoned carts released by a scheduled sweep
  • Coupons addressed by code, never by id
  • times_redeemed respected against max_redemptions, and unlimited coupons shown without a count
  • Short timeouts on cart-path calls
  • Loyalty failure never blocks a purchase
  • Failed transaction posts are queued, not dropped
  • Backoff with jitter on 5xx; no retry on 4xx
  • Requests and responses logged against the order id
  • Only data the customer consented to is sent
  • DELETE wired to your erasure process
  • Loyalty data authorised against the session, never a browser-supplied member id
  • Logs do not contain access tokens

Run these against sandbox and confirm each one.

ScenarioExpected
New customer signs upMember created, id stored
Existing member signs up againFound by search, updated — no “already registered” error surfaced
Customer logs in, already linkedNo Diller write
Customer logs in, not a memberOffer shown; nothing created
Signup with consent untickedNo member created
Signup with the SMS box ticked, email untickedreceive_sms: true, receive_email: false on the member
Profile editedAddress, departments, segments and both marketing flags survive the PUT
Order with no couponPoints awarded
Order with a couponDiscount applied, redemption recorded, reservation released
Payment fails after reserveReservation released; coupon usable again
Cart abandoned after reserveSweep releases the reservation; coupon usable again
Order cancelledPoints and coupon reversed
Partial refundPoints adjusted
Same order posted twiceSecond post returns is_ok: false; only one transaction
Diller unreachable at checkoutPurchase still completes
Erasure requestedDELETE sent, local id cleared

Test the coupon-release paths deliberately — force a payment failure, and abandon a cart at the payment step. They are the ones that silently destroy customer value if they are wrong, and the ones least likely to be exercised by accident.


  1. Get production credentials from the merchant.
  2. Change the four configuration values above. Nothing else.
  3. Confirm the token call succeeds against api.diller.app.
  4. Confirm GET /stores/{storeId} returns the right store — check the name, not just the status code.
  5. Put one real order through, end to end, with a real phone number you control. Production sends real SMS.
  6. Verify in the merchant’s Diller account that the member and transaction appear, in the right department and the right channel.
  7. Then open it up.

Do not migrate historical orders on day one. Get today’s flow correct first; backfill later, throttled, and after talking to us.


Watch, for the first fortnight:

  • Members created per day — a sudden zero means signup is broken
  • Transactions posted vs orders placed — the gap is your failure rate
  • 4xx rate by endpoint
  • Queue depth for failed transaction posts
  • Open reservations older than your sweep age — this should stay at zero. Each one is a coupon a customer can no longer use

That last one is worth an alert. It is invisible to your customers until it is not, and by then they have lost coupons. Count your own open reservations rather than comparing reserve calls with cancel-reservation calls: posting a transaction releases its reservation without any call from you, so the two would never match.


SymptomLikely cause
Every call 403Store id is not the one from GET /stores, or case was mangled
Every customer looks newEmpty-array miss treated as an error
Coupons disappear over timeReservations from abandoned carts never released
Address, departments or segments blanked after an editPUT left them out
Members stop receiving marketing after an editPUT left out receive_sms or receive_email
Members receiving marketing they never choseCreate left out receive_sms or receive_email
Merchant sees no online salesorigin.channel not set to "OnlineStore"
Points look wrong to customersShowing remaining_points, not points
Orders at odd timescreated_at sent without an offset
Duplicate transactionsexternal_id not deterministic, or origin changed between retries

When raising an issue, include the store id, the external_id of the affected order, the endpoint, the timestamp with timezone, and the full response body. That turns a multi-day thread into one reply.


Other Diller articles worth knowing about:

ArticleWhy
API — getting started for POSThe in-store side; useful if the merchant runs both
Swagger overviewGenerated endpoint index
Points2currency couponsPoints-as-money rewards
3rd party integrationsWooCommerce, Shopify, Magento, Klaviyo, Mailchimp

The machine-readable contract is in openapi/diller-api-v2.json.