Testing with test keys & magic numbers
Use a test key and reserved magic numbers to exercise the whole messaging API — sends, status callbacks, and replies — without a live number.
You don't need a provisioned, registered phone number to start integrating. Issue a test key, send to a set of reserved magic numbers, and Beep fully simulates the message lifecycle: nothing reaches a carrier, nothing is billed, but your status-callback and reply-destination integrations fire for real with genuine signatures. This mirrors the test-card model you may know from payment platforms.
Test keys vs. live keys
API keys carry a visible, unambiguous prefix:
| Prefix | Mode | Behavior |
|---|---|---|
beep_live_… | Live | Sends are delivered to real recipients through a carrier and are billed. |
beep_test_… | Test | Every send is simulated — never delivered, never billed. |
Every endpoint accepts either key; only the send behavior diverges. Create a test key from the dashboard (choose the Test key option) the same way you create a live key.
A test key:
- Routes every send to the simulator instead of a carrier. Single sends and batch sends behave identically.
- Needs no provisioned sender. If your workspace has no number yet (or you don't specify one), Beep uses a reserved synthetic sender, so a brand-new workspace can integrate before number approval completes.
- Is never billed and never counts toward usage or campaign analytics. Simulated traffic is tagged and excluded from all reporting.
- Is rate-limited like any other key. The per-API-key request rate limit applies normally to test keys; test sends skip only the provider-level send-throughput limiting and cost reservation (there is no real send).
Magic numbers only mean something under a test key. A live key sending to a magic number gets ordinary live treatment (and fails naturally, since the reserved numbers are non-routable). Test behavior can never leak into production, and a test key can never send a real SMS.
Magic numbers
Send to one of these reserved numbers (drawn from the fictional 555-01XX line range under non-geographic area code 500) to select a deterministic outcome:
| Number | Outcome | Simulated lifecycle |
|---|---|---|
+15005550101 | Success | queued → sent → delivered |
+15005550102 | Delivery failure | queued → sent → failed (carrier undelivered; error_code set) |
+15005550103 | Hard reject at send | immediate failed (invalid recipient; error_code set) |
+15005550104 | Opt-out | delivered, then a simulated STOP → opt_out callback + suppression |
+15005550105 | Landline | failed, error_code = LANDLINE |
+15005550106 | Success + inbound reply | delivered, then a simulated inbound reply routed to your reply destination |
+15005550107 | Success + inbound MMS | delivered, then an MMS webhook with a stored sample PNG |
+15005550108 | Success + unavailable MMS | delivered, then a reply with attachments_unavailable: true |
- Any other well-formed E.164 number on a test key defaults to the success path (
delivered), so you can use arbitrary numbers for happy-path flows. - Malformed numbers still return the normal validation error, so that path is testable too.
+15005550100 is the reserved synthetic sender and is never a valid recipient outcome.
How callbacks fire for simulated sends
Simulated sends drive the real status-callback machinery. If you've configured a status-callback URL (per send via status_callback, or as the workspace default), Beep POSTs a signed message.<event> for each transition in the outcome's lifecycle, with the same HMAC signing, payload shape, retry schedule, and idempotency as a live send. So a single magic number lets you verify your webhook end-to-end:
+15005550101deliversmessage.queued,message.sent,message.delivered.+15005550102deliversmessage.queued,message.sent,message.failed(with anerror_code).+15005550103and+15005550105deliver a singlemessage.failed.+15005550104deliversmessage.queued,message.sent,message.delivered, thenmessage.opt_out.
See Notification Settings for how to configure the callback URL and verify signatures.
Exercising reply destinations
Two magic numbers simulate inbound traffic so your reply destinations (webhook, zendesk, or beep_inbox) are exercised for real:
+15005550106(success + reply) — after delivery, Beep creates a simulated inbound reply and routes it through the same path a real reply takes. If the send (or workspace) has a webhook or Zendesk reply destination, your endpoint receives a signedmessage.receivedpayload.+15005550104(opt-out) — after delivery, Beep simulates an inboundSTOP. This applies suppression to the simulated recipient and emits amessage.opt_outstatus callback. Consistent with live behavior, a STOP is never forwarded to a webhook or Zendesk destination — it suppresses and reports, but does not deliver a reply.
Because opt-out suppression is applied only to the simulated test recipient, it never affects your live recipients.
Try it
This send simulates a successful delivery. Swap in your own test key and workspace id; change to to any magic number above to drive a different outcome.
curl -X POST https://app.beepmessaging.com/api/v1/messages \
-H "Authorization: Bearer beep_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Workspace-Id: 11111111-1111-1111-1111-111111111111" \
-H "Content-Type: application/json" \
-d '{
"status_callback": "https://app.example.com/hooks/beep-status",
"messages": [
{ "to": "+15005550101", "body": "Hello from test mode." }
]
}'{
"data": {
"id": "a1b2c3d4-0000-0000-0000-000000000001",
"to": "+15005550101",
"status": "queued",
"client_reference": null,
"person_id": "b2c3d4e5-0000-0000-0000-000000000002",
"phone_number_id": "+15005550100",
"mode": "test"
}
}Every send acknowledgment, batch item, and GET /api/v1/messages/{id} response includes a mode field ("test" or "live") — à la Stripe's livemode — so you can tell a simulated send from a real one. A beep_test_ key always reports "test"; a beep_live_ key reports "live".
The send acknowledgment reports a non-terminal queued status just like a live send. Poll the simulated terminal status with GET /api/v1/messages/{id}:
curl https://app.beepmessaging.com/api/v1/messages/a1b2c3d4-0000-0000-0000-000000000001 \
-H "Authorization: Bearer beep_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Workspace-Id: 11111111-1111-1111-1111-111111111111"For +15005550101 the message resolves to delivered; for the failure numbers it resolves to failed with the matching error_code.
When you're ready for production
Switch the Authorization header from your beep_test_ key to a beep_live_ key and send to real recipients once your sending number is provisioned and registered. No other code changes are required — the request and response shapes are identical.
Updated 23 days ago