Docs › Get started
Test mode
Every account ships with parallel hs_live_… and
hs_test_… keys. Test keys run a simulated carrier: numbers are
free and instant, compliance approves immediately, and the whole event
pipeline — webhooks included — behaves exactly like production.
What the simulator does
- Numbers — search and purchase return instantly and cost nothing; the inventory is synthetic but shaped like the real thing.
- Messages — sends are accepted and delivery receipts arrive moments later as real webhook events.
- Voice — inbound calls, answers, hangups, and voicemails can be simulated so your call-handling UI is testable without dialing a phone.
- Compliance — brands vet and campaigns approve immediately, with the same
status_changedevents production emits over days.
Magic numbers
Rehearse the failures you hope never happen:
| Value | Behavior |
|---|---|
+15005550001 | Send accepted, then delivery fails — message.failed webhook with carrier_rejected |
+15005550002 | Recipient replies STOP after the first delivery — rehearse opt-out handling |
+15005550003 | Dialed party never answers (click-to-call and ring targets) |
+15005550004 | Not portable — port-in checks fail with a reason |
+15005550005 | Port-in goes to action_needed after submission |
+15005550007 | Gathers time out — the party never presses anything |
+15005550008 | Media streams fail to start — call.stream.failed |
+15005550009 | The carrier rejects the send itself; after retries the message ends failed with send_failed |
+16054551234 | A real blocked exchange: calls return destination_not_supported — the high-cost (access-stimulation) block applies in test mode too |
E911 ZIP 00000 | Emergency-address registration is rejected |
| Any other number | Delivers instantly with a receipt; dialed parties answer within seconds |
A failure drill
# Same code as production — only the key differs. curl https://api.handset.dev/v1/messages \ -H "Authorization: Bearer $HANDSET_TEST_KEY" \ -d '{"from": "num_…", "to": "+15005550001", "body": "This one is doomed."}' # moments later, at your webhook endpoint: → message.failed "failure_reason": "carrier_rejected"
Mode isolation is strict: a test key can never
see live traffic, and vice versa — separate ledgers end to end.