Sandbox mode
Develop and test integrations against sk_test_* keys without touching
production data, payments, emails, or calendars. Webhook deliveries DO
fire to your configured endpoints so you can test your handler
end to end.
What sandbox mode does
When you authenticate with an sk_test_* key, three isolation layers
activate:
Layer 1 - Data isolation
You see only the per-agency sandbox tour pool (3 fixture tours: one Active, one Coming Soon, one Sold). You can read and write these tours freely. You cannot see your live production tours.
- Sandbox tour numbers are prefixed
snd_(e.g.snd_active_a1b2c3) - A sandbox key requesting a live tour number receives
404(not403) to prevent tour number enumeration across the sandbox boundary
Layer 2 - Side-effect shunting
Real charges, emails, calendar events, and storage uploads are replaced with safe equivalents:
| Integration | Sandbox behavior |
|---|---|
| Square | Uses Square's own sandbox environment (no real charges) |
| Mailjet | Writes a SandboxMailLog row (no real email sent) |
| Google Calendar | Returns a synthetic success response (no real event created) |
| Composio storage | Returns a synthetic storage URL (no real upload) |
Layer 3 - Rate-limit isolation
Sandbox requests use a separate Redis rate-limit bucket from live
requests. A runaway sandbox script or load test cannot consume your
sk_live_* rate limit budget.
Webhook deliveries in sandbox mode
Webhooks are intentionally NOT shunted. Sandbox webhooks fire to your
configured endpoints with "is_sandbox": true in the event payload.
This follows the Stripe model: your handler receives both sandbox and
live events at the same URL. The is_sandbox field lets you branch:
const payload = JSON.parse(rawBody);
if (payload.is_sandbox) {
// Test/staging branch - log to a separate table, skip real side effects
} else {
// Production branch
}
If you want a dedicated URL for sandbox traffic, configure two webhook
endpoints with different URLs and use the Environment toggle in
/manage/webhooks/endpoints to assign each to sandbox or live events.
Sandbox tour pool
On your first sk_test_* key creation, we automatically seed your
agency with 3 sandbox fixture tours. You can re-seed at any time via
POST /api/v1/agency/sandbox/seed (idempotent - will not duplicate).
The fixtures have realistic structure:
| Tour | Status | Tour number prefix |
|---|---|---|
| Fixture 1 | Active | snd_active_ |
| Fixture 2 | Coming Soon | snd_coming_soon_ |
| Fixture 3 | Sold | snd_sold_ |
All fixtures have: gallery placeholder images, synthetic Matterport URL, placeholder floorplan PDF, and synthetic Demo City CA addresses.
What you can do in sandbox mode
- Read, update, and cancel sandbox tours via the public API
- Upload media to sandbox tours (uploads complete with synthetic URLs)
- Read sandbox tour orders and users in your agency hierarchy
- Trigger all 18 webhook event types - deliveries fire to your
configured endpoints with
is_sandbox: true - Test your idempotency handling: replay any sandbox webhook delivery
via
/manage/webhooks/logs-> Replay - Run load tests against the sandbox endpoint without risk
What you cannot do in sandbox mode
- See or modify live (
sk_live_*) tours from a sandbox key (returns404) - Trigger real Square charges, real Mailjet emails, real Google Calendar events, or real Composio uploads
- Bypass the 500 requests/minute or 60 console-runs/minute sandbox rate limits
Migrating from sandbox to live
Once your integration works correctly against sandbox:
- Create a
sk_live_*key with the same scopes as your sandbox key. - Swap the
X-API-Key: sk_test_*header for the live key in your deployment environment. - Confirm webhook delivery to your production endpoint:
- Navigate to
/manage/webhooks/health - Verify your endpoint shows a recent successful delivery
- Navigate to
- Monitor
/manage/developer/analyticsfor unexpected error rate spikes in the first 24 hours after cutover.
Sandbox visibility in the dashboard
Sandbox requests are clearly labeled throughout the developer settings UI:
/manage/developer/access-log: sandbox requests show a "Sandbox" badge in the Environment column/manage/developer/analytics: filterable by Environment (All / Live / Sandbox)/manage/webhooks/logs: filterable byis_sandboxflag
Troubleshooting
See the operations runbook for common sandbox issues:
Sandbox environment troubleshooting
or the vault note runbooks/sandbox-environment-troubleshooting.md.
Common problems and quick fixes:
| Problem | Likely cause | Fix |
|---|---|---|
| Sandbox key returns 404 on all tours | Pool not seeded | POST /api/v1/agency/sandbox/seed |
| 429 on first sandbox request | Rate bucket confusion | Check inject_sandbox_state dependency |
| Revoked key still works | In-process cache (60s TTL) | Wait 60s or restart worker |
| Sandbox webhook not delivered | Endpoint configured for live-only | Enable Sandbox toggle in endpoint settings |