Sandboxes
Every developer project gets a sandbox: a fully isolated environment with its own database schema and its own pre-seeded restaurant. You develop and test against it with the exact same API, headers, and base URL you will use in production.
Both sandbox and production traffic go to https://api.ssppos.com/api/plugin/v1.
Your pik_ key alone decides which data plane you reach — so your plugin code
never needs a "am I in sandbox?" branch.
Provisioning
From your project page, choose Provision Sandbox (requires the owner or admin role). Provisioning returns two secrets, each displayed exactly once:
| Value | Format | Purpose |
|---|---|---|
| API key | pik_ + 64 hex characters (68 total) | Authenticating Plugin Data API calls |
| Webhook secret | 64 characters | HMAC-SHA256 signing, in both directions |
Store both immediately. Neither can be read back.
If a project has no plugin yet, provisioning also creates a scaffold plugin stub so the sandbox has something to attach its installation to. Scaffold plugins are inert in production but fully functional inside the sandbox — see Plugin Lifecycle.
What's in the box
Sandbox data is deterministic, so you can write tests against known state:
| Resource | Seeded content |
|---|---|
| Organization | One synthetic organization |
| Locations | 2 — Downtown Bistro (100 Main Street) and Uptown Grill (250 Park Avenue), both Montreal, QC |
| Menu categories | 4 — Appetizers, Mains, Drinks, Desserts |
| Menu items | 13 per location — 26 rows in total (e.g. Caesar Salad $12.99, Ribeye Steak $32.99, Espresso $3.99) |
| Orders | 6 — one in each status: CREATED, COOKING, SERVED, COMPLETED, PAID, CANCELLED |
| Customers | 3 per location |
| Inventory | 3 ingredients per location, with stock levels and reorder points |
| Tax | 15%, baked into the seeded order totals only — no tax rules are seeded, so orders you create yourself come back with zero tax |
The orders are ordered so the plugin-reachable starting states come first: you
always have a known open order (internal CREATED) and a known in_progress
order (internal COOKING) to drive through the
state machine.
Credentials
Regenerating the API key
Regenerate Key (owner/admin) mints a new pik_ and immediately revokes
the previous one. There is no overlap window — deploy the new key before or
immediately after regenerating.
Rotating the webhook secret
Rotate Secret (owner/admin) returns a new 64-character secret in plaintext, once. The old secret is invalidated immediately, so any webhook already in flight signed with it will fail verification on your side.
A safe rotation:
- Rotate in the portal and copy the new plaintext.
- Deploy it to your environment and restart.
- Fire a test webhook from the portal and confirm your endpoint verifies it.
Resetting
Reset Sandbox (owner, admin, or member) wipes the schema and re-seeds it from the template. What happens:
- All sandbox data returns to the deterministic seed above.
- Your
pik_API key is preserved — you do not need to redeploy. - Stop your plugin's traffic first. A portal reset goes straight from
activeinto the wipe without passing through theresettingstatus, so concurrentpik_calls meet a half-dropped schema and fail in unhelpful ways rather than receiving a clean retry signal. (The409 sandbox_resettingresponse documented under statuses is reachable through the in-Manager training-mode reset, not this one.) - Any outstanding SSP Manager operator sessions are invalidated; exchange a fresh token to log back in.
Lifecycle and expiry
| Property | Value |
|---|---|
| Idle TTL | 90 days for developer-portal sandboxes |
| Warning | sandbox.expiry_warning webhook fires 7 days before expiry |
| Reset the clock | Opening the sandbox in the portal, or resetting it. Plugin Data API traffic does not count — see below |
| Protection | protectSandbox pins a protection date, capped at 12 months |
| After expiry | The schema is dropped; the row is kept for a 30-day grace window |
This trips people up. The idle timer lives on the sandbox, and calls
authenticated with your pik_ key update the installation's usage stamp
instead — they leave the sandbox's last_accessed_at untouched.
So a sandbox your integration exercises every day is still deleted 90 days after its clock last moved. What does reset it: opening the sandbox in the Developer Portal, and resetting it.
If you are relying on a sandbox long-term, set a protection date with
protectSandbox (up to 12 months) rather than assuming traffic keeps it alive.
Watch for the sandbox.expiry_warning webhook 7 days out.
Protection auto-expires by design — once the date passes, normal idle cleanup
resumes. Pass null to clear it early.
Sandbox statuses
| Status | Meaning |
|---|---|
provisioning | Being created |
active | Ready for use |
resetting | Mid-reset — API calls return 409 |
paused | Temporarily inactive |
expired | Schema dropped by idle cleanup |
failed | Provisioning failed |
Only active sandboxes authenticate. Everything else returns
401 sandbox_not_ready, except resetting, which returns 409.
Testing webhooks
The portal's webhook tester (owner/admin) fires a real webhook at your
plugin's configured webhook_endpoint, built from actual sandbox data:
- Pick an event type (e.g.
order.created). - Send. You receive a log ID immediately.
- Poll that log ID for the delivery outcome.
| Status | Meaning |
|---|---|
received | Queued |
processing | A worker picked it up |
success | Your endpoint answered 2xx |
failed | Your endpoint answered non-2xx, or the request errored |
skipped | Not attempted — reason in error_message (no endpoint, no secret, event not subscribed, installation inactive) |
The log row is created before dispatch and updated in place across the three delivery attempts, so polling the same ID always shows the current state.
The tester hard-fails with no_sample_data if your sandbox has no rows for the
chosen event type. Reset the sandbox or create the relevant records first.
Opening SSP Manager against your sandbox
You can log into the real SSP Manager UI pointed at your sandbox, as that
sandbox's ORGANIZATION_ADMIN. This is how you see your plugin from the
restaurant's side: install it, configure it, and drive it through a real
service flow.
Organic event fan-out only reaches plugins whose listing is is_active —
which happens at publish. Sandbox and scaffold plugins are is_active = false
by design, so creating an order in the sandbox Manager will not deliver
order.created to your endpoint.
Use the portal's webhook tester to exercise your receiver; it targets your installation directly and bypasses that filter.
Use Open SSP Manager on the portal's sandbox page. Under the hood, the portal exchanges your session for a sandbox-operator JWT:
POST /api/developer/sandbox/{sandboxId}/jwt
Authorization: Bearer ssp_dev_YOUR_KEY # or your portal session
| Property | Value |
|---|---|
| Token lifetime | 24 hours |
| Rate limit | 30 requests/minute |
| Required role | Owner or admin on the project |
| Requires | Sandbox active; developer agreements accepted |
Failure modes are deliberately indistinguishable from each other: a sandbox that
doesn't exist, one you're not a member of, and one you lack the role for all
return the same 404 sandbox_not_found, so sandbox IDs can't be probed across
tenants.
| Error | HTTP | Meaning |
|---|---|---|
authentication_failed | 401 | No developer credential, or two credentials resolving to different accounts |
developer_agreement_required | 403 | Outstanding legal documents — see Developer Account |
sandbox_not_found | 404 | Missing, not yours, or insufficient role |
sandbox_not_ready | 409 | Not active, or provisioned before operator support |
operator_unavailable | 403 | The operator identity is missing or suspended (e.g. the owning key was revoked) |
Sandbox vs production
| Sandbox | Production | |
|---|---|---|
| Base URL | Same | Same |
| Key format | pik_ | pik_ |
| Data | Synthetic, resettable | Real restaurant data |
| Plugin listing status | Any status works, including scaffold | Only approved or active authenticate |
| Rate limit | 60/min per installation | 60/min per installation |
| Idle expiry | 90 days | None |
If a listing is suspended or deprecated, its production installations stop authenticating immediately — but sandbox installations are exempt, so you can keep working on the fix.
Next steps
- Getting Started — the end-to-end walkthrough
- Plugin Data API — what you can read and write
- Webhooks — the full event catalog