Skip to main content
This cookbook is for platforms such as 11za that already manage WhatsApp for many businesses. You add voice calling to each brand’s existing number through your backend. Each brand keeps using your application; it does not need a separate Voice Infra login.
Early access. WhatsApp calling is a controlled pilot. The public number-management APIs in this recipe are not deployed yet. Confirm their availability with Voice Infra before running the registration steps. General production rollout is not open: reliable two-way audio, production workspace activation and measured call capacity are still launch requirements. The examples below describe the integration contract; publishing this cookbook does not activate your workspace or numbers.

What you’ll build

  • An AI Calling section inside your application where a brand selects its WhatsApp number, configures its voice agent and tests calling.
  • Incoming WhatsApp calls answered by that brand’s agent.
  • Customer-requested callbacks after Meta confirms calling permission.
  • A brand-specific history of calls, transcripts and available results.
  • One partner workspace and combined Voice Infra bill across your brands.
For a partner with 2,000 brands, keep a separate agent and number registration for each brand. 2,000 registered brands does not mean 2,000 simultaneous calls. Agree and test concurrent-call capacity separately.

Is this like BYO telco?

Yes: you bring the brand’s existing WhatsApp number. You configure its Meta calling settings and register it with Voice Infra. The number stays with the brand’s Meta assets; no Plivo number purchase or number port is involved. Each number needs an integration, which your backend can create using our API. Voice Infra still approves each number’s ownership. Once the partner workspace is enabled, adding another approved number does not require a deployment for that brand. You need: an activated partner workspace, its backend API key and webhook signing secret, an authorized Meta integration for each number, and a public HTTPS webhook receiver. The examples use cURL and jq, Python 3.10+ with httpx, or Node.js 18+.

How it works

Keep your existing Meta messaging integration. Before changing SIP settings, check whether the number already uses another calling provider: changing the destination changes where its voice calls go. Meta charges remain under your existing Meta arrangement, separate from Voice Infra billing.

Step 1: Activate the partner workspace

Complete Go live and the billing setup with Voice Infra once for your partner workspace. Confirm your billing contact, commercial terms, funded balance and call limit. There is no public partner-signup endpoint that turns a sandbox key into production access. We supply these values through a private channel:
Load VOICE_BASE_URL, VOICE_API_KEY and VOICE_WORKSPACE_ID from your backend configuration or secret manager. Confirm access:
The wallet response includes balance_inr and currency; the voice catalogue is under data. 403 production_workspace_required means production activation is incomplete. Changing only the base URL does not upgrade a sandbox workspace. Confirm public number API availability before proceeding to number registration.
Keep the partner key on your backend. Brands must not receive this shared key or unrestricted access to your partner workspace. Our API enforces workspace ownership; your application enforces which brand can access each resource.

Store a mapping for every brand

Your database needs a durable mapping of:
Set BRAND_ID to that brand’s permanent ID in your application before running the creation examples. Run shell examples in Bash with set -euo pipefail so a missing value or failed request stops the sequence. Also retain secret-manager references for Meta access, your callback jobs, and historical agent/number assignments. Use your authenticated brand identity to select this mapping. A brand_id supplied in a request or in call metadata is a label, not authorization.

Step 2: Create the brand’s agent

An agent holds the brand’s instructions, greeting, voice, language and call duration. Use one agent per brand so that business information and future changes stay separate.

A real pilot agent example

The 11za pilot uses an agent named 11za WhatsApp Pilot, with voice neha, language en and a 120-second limit. Its job is to confirm two-way audio. This is the same kind of acceptance agent you should use before enabling a brand’s business workflow; it does not claim to access orders or customer records. Save this complete definition as agent.json:
This definition is adapted from the real test agent. Existing pilot identifiers are supplied privately to that partner. If you already have an agent in your workspace, read it with GET /v2/agents/{agent_id} and reuse its ID instead of creating a duplicate. IDs cannot be borrowed from another workspace. Check the selected voice in your workspace’s voice catalogue. Set the top-level language as well as any voice language. Replace the test instructions with the brand’s approved content before serving real customers. Order lookup or booking requires a real tool integration or supplied context; an instruction in the prompt does not create that integration.
The response is the agent object, including its id (agt_...). Reuse the same creation key and body after an uncertain request. Use PATCH /v2/agents/{agent_id} for subsequent edits.

Step 3: Configure the brand’s WhatsApp number

Your existing Meta integration performs this step. Check calling eligibility and the app’s access to the selected number with Meta. An existing messaging integration alone does not establish calling eligibility. Load META_GRAPH_VERSION, META_ACCESS_TOKEN and PHONE_NUMBER_ID from the brand’s authorized integration. PHONE_NUMBER_ID is Meta’s phone-number ID, not the WABA ID. BUSINESS_NUMBER is the brand’s E.164 number, including +. Generate meta-settings.json from the workspace and agent IDs you stored:
The ws hint is our workspace ID, and agent is our agent ID. Neither is an 11za brand ID or a Meta WABA ID. SDES and the additional codecs must match this integration. Check the response before continuing.

Retrieve the SIP password

Meta generates the number’s SIP password. Your brand does not invent it. Retrieve it using your authorized Meta token:
The password is calling.sip.servers[].sip_user_password for the entry whose hostname is sip.miraiminds.co. The SIP username is the business number in E.164. Store the password in your secret manager. The Meta token, SIP password and Voice Infra API key are three different credentials. Do not reset an already working SIP integration just to read its password. For an existing registered number, verify its settings and reuse its registration. Coordinate any move to a different workspace with Voice Infra.

Step 4: Register and activate the number

Register the number with our API. The example below builds a private request file without printing the SIP password. Set VOICE_WEBHOOK_URL to your real public HTTPS call-result receiver, implemented in step 7.
The required request fields are business_number, phone_number_id, agent_id, sip_username and sip_password. webhook_url and resolver_url are optional public HTTPS endpoints. Omit them until you implement them. The API determines workspace ownership from your key; do not submit a workspace ID in the body. The response includes these fields; values below illustrate the shape:
Store the returned id and version in the brand mapping. The full response also includes meta_settings_example; compare it with the settings you applied. Our API never returns the SIP password. Remove temporary password exports after they are securely stored and the integration is complete.

Approval and status

Send Voice Infra your brand identifier, business number, workspace ID and registration ID for ownership approval. A password is not needed in that handoff. Arrange a batch approval process when onboarding many brands; there is no customer self-approval API. Poll individual status with bounded backoff:
active is configuration status, not proof of audible speech or correct Meta routing. There is no activation webhook in this version. For inventory, use GET /v2/whatsapp/numbers?limit=100 and follow next_cursor; use individual GETs to reconcile activation. Avoid polling thousands of numbers every second.

Step 5: Receive incoming calls

An incoming call needs no POST /v2/calls request from your application:
  1. The customer taps the voice-call button in the brand’s WhatsApp chat.
  2. Meta delivers the call to Voice Infra.
  3. The registered agent answers and conducts the conversation.
  4. Events go to the number registration’s webhook_url, when configured.
Read the caller’s number from identity.caller_number in our call object. For incoming WhatsApp calls, to identifies the business; do not assume from contains the caller. identity.wacid correlates the call with Meta records.

Optional caller context

Set resolver_url on the registration if your backend should supply context before the agent answers. We send a signed request with this shape:
Verify X-Mirai-Signature over the raw bytes as described in step 7. Identify the brand from the registered business number and return HTTP 200:
Reference the supplied variables in the agent’s prompt, and share only context your backend is authorized to disclose. The resolver has a one-second budget. A valid {"allow":false} declines the call; a timeout or error falls back to the default agent. It is optional enrichment, not a fail-closed authorization or brand-spend gate. Sensitive actions need appropriate caller verification.

Step 6: Request a permitted callback

Your Call me button records the customer’s callback request. Native Meta calling permission is a separate requirement. Use your existing Meta messaging integration to request permission and process the authenticated confirmation, following Meta’s current messaging-window and template rules. Do not dial merely because a generic button was clicked or a cached expiry says permission is valid. Check the current Meta verdict for the actual brand and recipient:
CUSTOMER_WHATSAPP_DIGITS is the country code and number without +. Continue only when the last command succeeds. Meta can still refuse the call if a permission or quota changes afterward. Use the current provider verdict rather than hard-coded country lists or permission lifetimes.

Store the callback job before dialing

Your backend must keep a durable job with a unique (brand_id, callback_id) pair. Freeze the recipient, agent, business number, request body, original timestamp and idempotency key. Only create a job for an outstanding customer request; receiving permission alone does not authorize a fresh callback. Save this call request as callback.json, substituting values from that job:
Use a real receiver URL, the registered brand number and the approved recipient. variables and metadata are optional. agent_id, to, route: "whatsapp" and the registered from select this call. Include webhook_url for outbound results; the registration’s webhook is used for inbound results.
The response is HTTP 202 with the call object, including id (call_...) and status. Acceptance does not mean the customer answered. Voice Infra handles the SIP signaling and audio; you do not send SDP to Meta’s /calls endpoint for this integration.

Retry without calling twice

  • Process permission webhooks through your durable queue. Duplicate deliveries must select the same callback job.
  • Retry uncertain API outcomes with the same body and idempotency key. Do not mint a new callback ID to get around a timeout.
  • Retain completed jobs beyond our 24-hour call-idempotency window. Stop automatic retries before that expires; an old webhook must not become a fresh dial.
  • Persist the returned call ID and read its result. Do not automatically redial a completed or failed call, or move it to another channel.

Step 7: Receive and display results

Voice Infra sends signed webhooks to your HTTPS receiver. The envelope contains an event ID, type, creation time and data.call:
Verify X-Mirai-Signature using the workspace signing secret and the exact raw request bytes. It is different from Meta’s webhook signature and from your API keys. This Python verifier also works on resolver request bytes:
In your HTTP handler, verify before parsing JSON. Insert the authenticated event into a durable inbox with a unique (workspace_id, event_id) key, then return 2xx. Process the inbox asynchronously. If storage fails, return a retryable failure instead of acknowledging an event you have lost. For outbound calls, the stored callback job establishes the brand’s ownership. For inbound calls, match the business number and agent against your stored brand mapping. Keep historical mappings for late events. Do not assign a call to a brand solely from client-provided metadata. Handle call.started, call.completed, call.failed, call.aborted and, where applicable, call.voicemail. Post-call processing may emit call.processed when configured. Use data.call.status and ended_reason to distinguish outcomes; there is no call.no_answer event. Do not let a late call.started overwrite a terminal result. Check brand ownership before every read or control operation. Transcript, recording and analysis availability can lag behind the call ending.

Step 8: Operate one workspace across many brands

Combined billing

One funded partner wallet covers the brands’ calls. Use GET /v2/wallet for balance and GET /v2/wallet/transactions for reconciliation. Associate settled call costs with your stored brand mapping and use those records for your own resale billing. Voice Infra does not create separate brand invoices or balances inside this shared workspace. Confirm the workspace’s current pricing and wallet terms. Do not copy a pilot charge as a universal rate. Meta charges are separate. Coordinate changes to keys and the workspace’s pinned inbound calling configuration with Voice Infra before changing production billing assumptions. An exhausted shared balance can affect every brand. Monitor it centrally. Per-brand dashboards and fair allocation are your application policies. Strict per-brand inbound spend limits are not provided by this shared-workspace recipe; the optional resolver is not a reliable hard budget gate.

Onboard in batches

Use a resumable queue and save IDs after each successful step. Back off on 429/503 responses. Arrange ownership approval in batches, then test and enable brands gradually. A database test with 2,000 registrations does not establish the simultaneous-call capacity your customers can use. Measure expected calls per second, concurrent calls and average call duration with Voice Infra before agreeing traffic limits. Monitor connection success, time to greeting, missing or one-way audio, dropped calls, provider errors, callback backlog, webhook delivery and wallet settlement. Healthy HTTP endpoints and a completed status do not prove both parties heard the conversation.

Update or disconnect a brand

For example, update the result destination using the version from your latest number read:
DELETE blocks new calls immediately while existing calls can finish. Retain call and billing history. It is not a temporary pause with a public re-enable API; coordinate restoration or workspace migration with Voice Infra.

Test it

Use the 120-second test agent from step 2 on one approved number. Record a call ID for every test:
  1. Call the number in WhatsApp. Hear the greeting, say a short sentence, and confirm the agent repeats its meaning. Repeat to catch intermittent failures.
  2. Make a genuine callback request through your app, accept native Meta calling permission, and confirm your job worker starts one call with audible speech.
  3. Replay the permission webhook. Confirm it reuses the job and does not ring the customer again.
  4. Test missing or revoked permission, a failed call, and a duplicate result webhook. Confirm your app records the correct outcome.
  5. Check signed result delivery, available transcript/recording and settled usage.
  6. Confirm another brand cannot read, change or cancel those calls through your application.

Troubleshooting

Privacy and compliance

Tell callers they are speaking with a voice agent and explain recording or transcript retention where applicable. Keep keys, SIP passwords and recording links out of browsers, application logs and public documents. Apply brand-level access checks to stored results and set retention rules appropriate to your use case. Meta calling permission is distinct from recording consent. Evaluate the privacy and calling requirements that apply to your operation, including GDPR, HIPAA or TCPA where relevant. This recipe does not establish compliance or authorize handling regulated data. Confirm the applicable platform terms and your organization’s requirements before enabling those workflows.

Production checklist

  • Production workspace activation, central funding, commercial terms and the public APIs you need are confirmed.
  • Every brand’s number and agent mapping is correct, approved and active.
  • Repeated incoming and permitted outgoing calls have audible two-way speech.
  • Your actual Meta permission webhook, durable callback worker, result receiver and brand dashboard have been tested together.
  • Duplicate and replayed events cannot start extra calls or corrupt final results.
  • Concurrent and long calls pass within the measured limit, including provider failure, wallet exhaustion, alerting and recovery tests.
  • Credential rotation, brand updates and disconnects work as documented.
  • Brand isolation, retention, caller notices and settled usage are verified.
Do not enable all brands because the first number registers successfully. Keep each brand in setup until real call acceptance passes, then expand in measured batches. Number approval, partner production activation and reliable audio remain required even when every API request succeeds.

References

Use the current Meta documentation and responses for eligibility, permission messages and provider limits. Your existing Meta integration owns those steps.