> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracklysms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox & Testing

> Simulate sends and signed webhooks with a test API key — every live gate runs, but nothing is delivered or billed.

A **sandbox key** lets you build and test the full send path — validation, eligibility, webhooks, signature verification — without delivering a message or incurring a charge. Sends run every gate a live send runs, then short-circuit to a simulated result.

## Getting a sandbox key

Create a sandbox key from the dashboard under **Settings → API Keys** with **Sandbox mode** enabled. A key is treated as a sandbox key when it carries the sandbox flag or its value starts with `trk_test_`.

<Note>
  The partner [key-mint endpoint](/api-reference/v2/accounts/create-api-key) issues **live** keys — there is no sandbox option on it yet. Provision sandbox keys from the dashboard.
</Note>

Use a sandbox key exactly like a live key — the same endpoints, the same headers. The response tells you it's simulated:

```json theme={null}
{
  "success": true,
  "sandbox": true,
  "status": "simulated",
  "simulated_outcome": "delivered",
  "message_id": "sandbox_1234_1seg",
  "segment_count": 1,
  "encoding": "GSM-7"
}
```

No `MessageSent` row is written, nothing is queued to the carrier, and no usage is billed.

<Note>
  Sandbox keys are **read-only for the account hierarchy** — creating children, minting keys, and suspending all require a live parent key (`403 sandbox_read_only`).
</Note>

## Deterministic outcomes

Send to a reserved recipient number to force a specific outcome. Any other recipient simulates `delivered`.

| Send `to`      | `simulated_outcome` | Webhook event fired |
| -------------- | ------------------- | ------------------- |
| `+15005550100` | `delivered`         | `message.delivered` |
| `+15005550101` | `failed`            | `message.failed`    |
| `+15005550102` | `replied`           | `message.reply`     |
| `+15005550103` | `opted_out`         | `contact.opted_out` |
| *(any other)*  | `delivered`         | `message.delivered` |

A `failed` outcome also returns `error_code: "SANDBOX_CARRIER_REJECT"` and an `error_description`, mirroring a real carrier rejection.

## Simulated signed webhooks

A **single** sandbox send fires the matching [signed webhook](/api-reference/v2/webhooks/signing) to your account's configured endpoints — a real HTTP POST with a real `X-Trackly-Signature`, with `data.sandbox: true` on the envelope. This is the end-to-end way to prove your signature verification works before going live: send to `+15005550101`, receive a `message.failed` event, and confirm your verifier accepts it.

<Note>
  **Bulk sends do not fire webhooks.** A bulk simulation would fan out one POST per message; to test webhook handling, use single sends to the reserved numbers.
</Note>

Webhook dispatch is best-effort — if your endpoint is down, the simulated send still returns `201`.

## Every live gate still runs

A sandbox pass means a live send would pass too, because the simulation runs the same checks first and returns the same error codes on failure:

* Payload shape and E.164 validation (`missing_to`, `invalid_phone`, …)
* Sending-list ownership (`list_not_found`)
* BYOC webhook verification (`webhook_not_configured`)
* Double opt-in (`pending_confirmation`, `doi_expired`)
* Suppression / opt-out (`contact_suppressed`)
* Free-tier BYOC requirement (`free_tier_non_byoc`)

<Note>
  Two side-effecting behaviors are **not** simulated: warm-up (its eligibility check would claim a ramp slot) and `wrap_links` URL shortening (it persists short links). A live `wrap_links=true` send may therefore report a different segment count than its sandbox preview.
</Note>

## Related

<CardGroup cols={2}>
  <Card title="Preflight" icon="list-check" href="/api-reference/v2/messages/preflight">
    Check eligibility, segments, and cost without sending — works with live keys too.
  </Card>

  <Card title="Webhook signing" icon="shield-check" href="/api-reference/v2/webhooks/signing">
    Verify the signature on the webhooks your sandbox sends fire.
  </Card>
</CardGroup>
