Getting a sandbox key
Create a sandbox key from the dashboard under Settings → API Keys with Sandbox mode enabled. A key whose value starts withtrk_test_ is also always treated as a sandbox key. Dashboard-created sandbox keys use the ordinary trk_ prefix, so a client cannot tell a sandbox key from a live key by looking at it: call GET /v2/auth/whoami and read api_key.sandbox.
The partner key-mint endpoint issues live keys only. Provision sandbox keys from the dashboard.
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).Deterministic outcomes
Send to a reserved recipient number to force a specific outcome. Any other recipient simulatesdelivered.
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 to your account’s configured endpoints — a real HTTP POST with a realX-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.
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.
201.
Live validation parity
A sandbox send runs the same side-effect-free 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) linkOverridesshape, strictwrap_linksboolean, supported arm, public HTTPS URL policy, 2,048-character URL limit before and after macro expansion, Partnerships entitlement, and active account-owned offer eligibility (invalid_wrap_links,link_override_invalid,link_override_unsafe_url,link_overrides_require_wrap_links,offer_not_found,partnership_offer_not_found,partnerships_not_enabled). With a non-emptylinkOverridesobject,wrap_linksmust be a JSON boolean and must betrue; requests without effective overrides retain legacy coercion behavior.
400, 403, or 404 status as a live send. In a sandbox bulk request, partnerships_not_enabled rejects the whole request with 403; the 400 parser/URL errors and 404 offer errors appear per message in errors[], and the request returns 201.
Two side-effecting behaviors are not simulated: warm-up and link minting.
linkOverrides are validation and preload only: no short link, self-affiliate, or offer-access state is created, and {{linkN}} stays unsubstituted in the body used for segment measurement. Sandbox returns no_domain_configured when a referenced override has no usable shortener and offer_access_not_active when existing access is paused or rejected; it does not create missing access. A live wrap_links=true send may report a different segment count than its sandbox preview.Related
Preflight
Check eligibility, segments, and cost without sending — works with live keys too.
Webhook signing
Verify the signature on the webhooks your sandbox sends fire.