Every programme has a sandbox: a second, invisible programme that mirrors the live one. A sticky_test_… key writes there and nowhere else.
What the sandbox mirrors
| Copied from live | Not copied |
|---|---|
| Active earning rules and boosts, tiers | Members, orders, ledgers, balances |
| Programme settings: return windows, redemption and expiry policy | Campaigns (a boost that belongs to a campaign is copied without its campaign dates and audience) |
| The programme's name, currency and points name | Webhook endpoints (register test ones separately) |
| Branding (when the sandbox is first created) | Portal links and the hosted rewards page |
| Your plan and its limits (the sandbox follows live) |
So an order.paid that earns 250 points in test will earn 250 in live, provided the rules have not changed since the last wipe and no campaign applies.
Wiping it
On the Developers page of the admin, the Test sandbox card's Wipe test data button deletes every sandbox member, event, ledger entry and redemption, then re-copies the current live rules, tiers and programme settings. Use it before a test run so you start from a known state — and after you change rules in live, so the sandbox matches again. The data wipe is one atomic operation; nothing partial is ever left behind.
Writing integration tests against it
- Keep a
sticky_test_…key in your CI secrets, never a live one. - Give each test run a unique customer namespace —
email: "ci-<run id>-<n>@example.test"— so parallel runs never collide and you never need to wipe mid-run. - Send the event, then poll
GET /v1/events/{id}untilstatusiscompletedrather than sleeping: processing is asynchronous. - Assert on
GET /v1/members/{id}balances, not on the202response. - A test endpoint receiving webhooks from the sandbox is the only reliable way to test your handler end to end;
POST /v1/webhook_endpoints/{id}/testsends awebhook.testdelivery on demand.
ID=$(curl -s https://stickytier.com/v1/events -H "Authorization: Bearer $TEST_KEY" \
-H "Content-Type: application/json" -d @order.json | jq -r .data.id)
until [ "$(curl -s https://stickytier.com/v1/events/$ID -H "Authorization: Bearer $TEST_KEY" | jq -r .data.status)" = completed ]; do sleep 0.5; doneThings that behave differently in test
POST /v1/members/{id}/portal-linksanswers409— there is no sandbox rewards page. The reason isno_portal_domainwhen the sandbox has no rewards-page address (it usually doesn't; that check comes first), otherwisetest_mode.- There is no sandbox rewards page, so member sign-in cannot be tested there; drive members through the API.
- Plan limits apply in the sandbox as they do in live.
- The welcome bonus is never paid in the sandbox.