Testing & Sandbox
Build against sandbox mode, simulate verified sessions with skip_verification, test webhooks through a tunnel, and run the going-live checklist.
Sandbox mode lets you exercise your entire integration — session creation, webhooks, VP tokens, returning-user flows — without verifying a real user. This page covers sandbox mode, the skip_verification flag, local webhook testing, CI suites, and the checklist for moving an integration onto a live organization.
Sandbox mode
Stile runs a single environment: every organization is live by default. For building and testing, enable sandbox mode on a dedicated testing organization — an org-level setting you turn on through your dashboard org settings or by contacting support. The same stile_sk_… / stile_pk_… keys work everywhere; what changes is the organization they belong to.
| Sandbox org | Live org | |
|---|---|---|
| Verifications | Real or simulated — skip_verification completes sessions instantly | Real verifications only |
| Webhook endpoint | Optional | Required — at least one active endpoint before sessions can be created |
| Webhook URL scheme | HTTPS or HTTP | HTTPS only |
| Billing | Free, capped at 500 verifications/month | Billed beyond the free tier (100 verifications/month by default) |
Both follow the same per-key rate limit — 1,000 requests/min for secret keys, 100/min for publishable keys. Use a sandbox org for development, CI pipelines, and staging environments. Point your integration at a live org when you're ready to verify real users — and follow the going-live checklist below when you do.
Sandbox quota
Sandbox mode is capped at 500 verifications per calendar month per organization. When you hit the cap, session creation returns 402 with code test_quota_exceeded; the quota resets on the 1st of the next month. Sandbox verifications are unbilled, so this cap keeps a runaway CI or load test in check — if you need more volume, run those against a live org.
Live orgs require webhooks
Creating a verification session in a live (non-sandbox) organization will fail with
webhook_required if the org has no active webhook endpoints. Register one in the
dashboard or via the Webhook Endpoints
API before going live. Sandbox orgs are exempt.
The skip_verification flag
In a sandbox org, pass skip_verification: true when creating a session to skip the entire camera/ID scanning flow. The session transitions straight to verified, fires webhooks, and issues a VP token — all without user interaction.
This is useful for:
- Testing webhook handlers end-to-end
- Validating VP token flows and returning-user logic
- Exercising checkout integrations in CI
- Verifying email binding and OTP behavior
curl -X POST https://api.stile.id/v1/verification_sessions \
-H "Authorization: Bearer stile_sk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "age",
"workflow_id": "wf_YOUR_WORKFLOW_ID",
"email": "test@example.com",
"skip_verification": true
}'import Stile from "@stile/node";
const stile = new Stile(process.env.STILE_API_KEY!); // stile_sk_...
const session = await stile.verificationSessions.create({
type: "age",
workflow_id: "wf_YOUR_WORKFLOW_ID",
email: "test@example.com",
skip_verification: true,
});The response is identical to a real verified session — same shape, same webhook events, same VP token issuance. Whatever you build against it works unchanged against real verifications.
Sandbox only
The skip_verification flag is rejected in a live org — creating a session with
skip_verification: true against a non-sandbox organization returns a 400 with code
parameter_invalid. This is deliberate: there is no way to mint a verified session in a live org
without a real verification.
Local webhook testing
During development your server runs on localhost, which isn't reachable by Stile's webhook infrastructure. Use a tunnel to expose it.
# 1. Start your local server
npm run dev # listening on port 3000
# 2. In a separate terminal, start ngrok
ngrok http 3000
# 3. Copy the forwarding URL (e.g. https://abc123.ngrok-free.app)
# 4. Register it as a webhook endpoint:
curl -X POST https://api.stile.id/v1/webhook_endpoints \
-H "Authorization: Bearer stile_sk_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok-free.app/api/webhooks",
"enabled_events": ["verification_session.verified"]
}'# 1. Start your local server
npm run dev # listening on port 3000
# 2. Start the tunnel
cloudflared tunnel --url http://localhost:3000
# 3. Use the generated *.trycloudflare.com URL as your webhook endpointOnce the endpoint is registered, create a session with skip_verification: true to trigger a real, signed verification_session.verified delivery against your local handler — including the Stile-Signature header, so you can test signature verification exactly as it runs in production.
Dashboard shortcut
You can also register webhook endpoints directly in the dashboard instead of using the API.
E2E testing in CI
skip_verification: true is designed for end-to-end suites — it lets you exercise the full integration without camera interactions:
- Session creation and status transitions
- Webhook delivery and signature verification against your real handler
- VP token issuance and returning-user reuse
- Email binding and OTP flows
A typical Playwright/Cypress flow:
- Create a session with
skip_verification: truevia your backend. - Assert your webhook handler marked the order verified.
- Assert your UI unlocked.
Keep an eye on the 500/month sandbox quota if your suite runs on every commit.
Sandbox data hygiene
A few best practices to keep your sandbox organization clean:
- Use unique emails per test run — append a timestamp or UUID (e.g.
test+1712345678@example.com) to avoid collisions with previous runs. - Sandbox sessions expire after 24 hours — unverified sandbox sessions are automatically cleaned up, so you don't need to delete them manually.
- Isolate testing in a sandbox org — keep your CI and manual testing in a dedicated sandbox organization, separate from the live org that serves real users.
Going live checklist
When your integration works end-to-end in a sandbox org, work through these steps before pointing it at a live organization.
Point at a live org
Use the stile_sk_… key for your live organization on the server and its stile_pk_… key in the widget. Keys are managed at dashboard.stile.id/api-keys — secret keys are shown once at creation, so store them in your environment, never in code. A live org is billed beyond the free tier (100 verifications/month by default).
Register a webhook endpoint (required)
A live org requires at least one active webhook endpoint before you can create sessions — without one, POST /v1/verification_sessions returns 400 with code webhook_required. Register a public HTTPS URL in the dashboard or via the Webhook Endpoints API, and save the signing secret. See the Webhooks guide for setup and retry behavior.
Verify webhook signatures
Treat the signed webhook — not client-side events — as the source of truth for granting access. Your handler must verify the Stile-Signature header against the raw request body before processing any event. Use stile.webhooks.fromRequest() from @stile/node, or grab a copy-paste handler for your stack from Webhook Verification.
Remove skip_verification
Audit your session-creation code paths for skip_verification: true. In a live org the flag is rejected with 400 parameter_invalid, so any leftover sandbox shortcut becomes a hard failure on your first production session. Gate it behind an environment check if your test suite still needs it.
Handle 402 and 429 responses
Make sure your error handling covers the billing and rate-limit cases: 402 means test_quota_exceeded in a sandbox org or billing_suspended in a live org — surface these rather than retrying. 429 (rate_limit_exceeded) is retryable: respect the Retry-After header and back off exponentially. The rate limit is the same everywhere (1,000 requests/min for secret keys, 100/min for publishable keys), so a load pattern that throttles in staging will throttle the same way in production — but the handling should be in place either way. See Error Handling for the full retry policy.