Sandbox
Test WhatsApp sending, replies and webhooks against Zernio's shared sandbox number before buying a number of your own.
By the end of this page, you have sent a WhatsApp template to your own phone from Zernio's shared sandbox number and received the reply. You can then exchange free-form messages with it for 7 days.
Sandbox limits
| Property | Value |
|---|---|
| Shared number | One Zernio-owned WhatsApp number, returned by GET /v1/phone-numbers under sandbox |
| Verification | Reply-based: Zernio sends a template to your test phone, you reply, the session activates |
| Templates allowed | One, locked: sandbox_start (no variables) |
| Free-form text and interactive messages | Yes, inside the 24-hour customer service window the reply opens |
| Test phones per user | 1 (one active session at a time) |
| Daily caps per user | 50 messages, 5 distinct recipients (in practice 1) |
| Pending session TTL | 24 hours |
| Activated session TTL | 7 days |
| Billing | Free: no number charge, no message charge |
The sandbox is for testing. Get a number of your own to send to more than one recipient, sync templates from WhatsApp Manager or run production traffic.
The number is shared, so a phone can only be targeted once its owner has proved control of it with a WhatsApp reply.
Before you start
You need an API key and a phone with WhatsApp.
No number purchase, no WABA and no Meta review is involved, and nothing is billed.
Step 1: Discover the sandbox
Call GET /v1/phone-numbers. The sandbox number, the accountId you send from and the locked template come back under sandbox.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: numbers } = await zernio.phonenumbers.listPhoneNumbers();
if (!numbers.sandbox) throw new Error('Sandbox not configured on this environment');
const sandbox = numbers.sandbox;Response (200), trimmed to the sandbox:
{
"numbers": [],
"connected": [],
"sandbox": {
"phoneNumber": "+12029087457",
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"template": { "name": "sandbox_start", "language": "en" },
"isSandbox": true
}
}sandbox.accountId is the accountId for every send below.
Step 2: Start activation for your phone
Call POST /v1/whatsapp/sandbox/sessions with phone.
This creates a pending session and at once sends the sandbox_start template from the sandbox number to that phone.
The phone's owner opens WhatsApp and replies. Any text reply, including a tap on the "Reply and verify" button, flips the session to active.
const { data: started } = await zernio.whatsappsandbox.createWhatsAppSandboxSession({
body: { phone: '+13105551234' }
});
console.log(started.session.id, started.session.status);Response (200):
{
"session": {
"id": "66f7d4e5f6a7b8c9d0e1f2a4",
"phoneE164": "13105551234",
"status": "pending",
"expiresAt": "2027-01-02T09:00:00Z",
"activatedAt": null,
"createdAt": "2027-01-01T09:00:00Z"
},
"sandboxNumber": "+12029087457"
}Tell the user to check WhatsApp on that phone and reply. Posting the same phone again is idempotent and resends the template.
Step 3: Wait for the reply
Activation is automatic. Poll GET /v1/whatsapp/sandbox/sessions, or listen for message.received on your webhook endpoint, which fires for the reply itself.
const { data: sessions } = await zernio.whatsappsandbox.listWhatsAppSandboxSessions();
const session = sessions.sessions[0];
if (session?.status === 'active') {
console.log('Verified at', session.activatedAt);
}Response (200), once the phone has replied:
{
"sessions": [
{
"id": "66f7d4e5f6a7b8c9d0e1f2a4",
"phoneE164": "13105551234",
"status": "active",
"expiresAt": "2027-01-08T09:02:30Z",
"activatedAt": "2027-01-01T09:02:30Z",
"createdAt": "2027-01-01T09:00:00Z"
}
],
"sandboxNumber": "+12029087457"
}Step 4: Send to the activated phone
The phone is now a normal WhatsApp conversation in your inbox.
Call POST /v1/inbox/conversations with the sandbox accountId, the phone as participantId (digits only, matching phoneE164) and the locked template.
A phone with no active session is rejected.
const { data: opened } = await zernio.messages.createInboxConversation({
body: {
accountId: sandbox.accountId,
participantId: '13105551234',
templateName: sandbox.template.name,
templateLanguage: sandbox.template.language,
templateParams: []
}
});
const conversationId = opened.data.conversationId;Response (201):
{
"success": true,
"data": {
"messageId": "wamid.HBgLMTMxMDU1NTEyMzQVAgARGBI5QTNEMEY3RjQ4RjE2QjA3QzYA",
"conversationId": "66c3d2ae7b4f6c8d0e1f2a3b",
"participantId": "13105551234"
}
}Each reply from the phone opens a 24-hour customer service window.
Inside it, POST /v1/inbox/conversations/{conversationId}/messages with accountId and message sends free-form text, attachments and interactive messages, exactly as in any other conversation (WhatsApp inbox).
Replace the activated phone
Revoke the current session with DELETE /v1/whatsapp/sandbox/sessions/{sessionId}, then start activation for the new phone:
curl -X DELETE "https://zernio.com/api/v1/whatsapp/sandbox/sessions/66f7d4e5f6a7b8c9d0e1f2a4" \
-H "Authorization: Bearer $ZERNIO_API_KEY"
curl -X POST "https://zernio.com/api/v1/whatsapp/sandbox/sessions" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone": "+442071234567"}'Response (200), the revoke:
{
"success": true
}Response (200), the new activation, which sends the template to the new phone at once:
{
"session": {
"id": "66f7d4e5f6a7b8c9d0e1f2a5",
"phoneE164": "442071234567",
"status": "pending",
"expiresAt": "2027-01-02T09:10:00Z",
"activatedAt": null,
"createdAt": "2027-01-01T09:10:00Z"
},
"sandboxNumber": "+12029087457"
}Messages already exchanged with the old phone stay in the inbox; revocation only blocks future sends.
If it fails
A 400 from Step 2 while another phone still has a session names that phone so you can revoke it first:
{
"error": "You can only test one phone at a time. Revoke +13105551234 first",
"code": "invalid_field_value"
}The other 400s on the sandbox endpoints:
| Error | Cause | Fix |
|---|---|---|
invalid_field_value "This phone is not activated for sandbox sends" | The recipient has no active session for the calling user | Run Steps 2 and 3 for that phone first |
invalid_field_value "Cannot activate the sandbox number itself" | phone is the sandbox number | Use a real, different WhatsApp number |
invalid_field_value "Could not send the activation message..." | Meta rejected the template send (the number is not on WhatsApp, the WABA is paused); Meta's message is included | Check the phone is a WhatsApp account on a registered number |
rate_limited | The 50 messages or 5 recipients per 24 hours cap | Wait for the rolling window, or use a number of your own |
403 from any /sandbox/* endpoint, code: "INBOX_REQUIRED" | The team is on legacy billing without inbox access | Move to usage-based billing, which includes the inbox on every account, or start the free 7-day trial the error offers while trialAvailable is true (inbox access) |
Related
- WhatsApp phone numbers: get a number of your own when the sandbox is not enough.
- WhatsApp inbox: what you can send inside the 24-hour window.
- Inbox webhooks:
message.receivedfor the activation reply and every message after it. - Webhook account scope: set
accountIdsto the sandbox account id for a sandbox-only endpoint. A scoped webhook no longer receives events with no account, such aswhatsapp.number.*andverification.*. - Sandbox API: the 3 session endpoints.
Phone Numbers
Read a WhatsApp number's live status from Meta, tell a dead channel from a live one, and register a number whose two-step PIN blocked it.
Inbox
Receive and reply to WhatsApp conversations through the inbox API, send templates, interactive and commerce messages, and track delivery with webhooks.