Skip to main content
This cookbook is for platforms 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 and 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 yet. It still depends on reliable two-way audio, production workspace activation and measured call capacity. The examples below describe the integration contract. Your workspace and numbers are not active just because this page is published.

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 one combined Voice Infra bill for all your brands.
For a partner with 2,000 brands, keep a separate agent and number registration for each brand. Registering 2,000 brands does not give you capacity for 2,000 simultaneous calls, so agree on concurrent-call capacity and test it separately.

Is this like BYO telco?

Yes. You bring the brand’s existing WhatsApp number, configure its Meta calling settings and register it with Voice Infra. The number stays with the brand’s Meta assets, and you do not buy a Plivo number or port the number. Your backend can create the integration each number needs through our API, but Voice Infra still approves ownership of each number. 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, because 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, and 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. Before you register numbers, confirm that the public number APIs are available.
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. Select this mapping using the brand identity you authenticated. A brand_id supplied in a request or in call metadata is only a label and must not be used to authorize access.

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.

An acceptance test agent

Start each number with a test agent like this one, with voice neha, language en and a 120-second limit. Its job is to confirm two-way audio, and it does not claim to access orders or customer records. Use it before you enable a brand’s business workflow. Save this complete definition as agent.json:
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. You cannot use an ID 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. Writing an instruction in the prompt does not create that integration.
The response is the agent object, including its id (agt_...). If you are not sure a request went through, retry it with the same creation key and body. Use PATCH /v2/agents/{agent_id} for later edits.

Step 3: Configure the brand’s WhatsApp number

Your existing Meta integration does this step. With Meta, check that the selected number is eligible for calling and that your app can access it. A messaging integration on its own does not make a number eligible for calling. 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 your platform’s 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, so the brand does not choose one. Retrieve it with 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 the public HTTPS URL of the call-result receiver you build 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. Leave them out until you have built them. The API works out workspace ownership from your key, so do not send a workspace ID in the body. The response includes these fields (the values are examples):
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. Once the password is in your secret manager and the integration is complete, delete the temporary files that hold it.

Approval and status

To get ownership approved, send Voice Infra your brand identifier, business number, workspace ID and registration ID. You do not need to send the password. If you are onboarding many brands, arrange batch approval. There is no self-approval API for customers. Poll each number’s status with bounded backoff:
active means the configuration is in place. It does not prove that speech is audible or that Meta routes calls correctly. There is no activation webhook in this version. For inventory, use GET /v2/whatsapp/numbers?limit=100 and follow next_cursor. To reconcile activation, use individual GETs. Do not poll thousands of numbers every second.

Step 5: Receive incoming calls

Your application does not send a POST /v2/calls request for an incoming call:
  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, if one is set.
Read the caller’s number from identity.caller_number in our call object. For incoming WhatsApp calls, to identifies the business, so do not assume from contains the caller. identity.wacid links the call to Meta’s records.

Optional caller context

Set resolver_url on the registration if you want your backend to 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, while a timeout or error falls back to the default agent. Treat the resolver as optional enrichment. Because it does not fail closed, it cannot act as an authorization check or a per-brand spend limit. Verify the caller before any sensitive action.

Step 6: Request a permitted callback

Your Call me button records the customer’s callback request. You also need native Meta calling permission, which is separate. 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. Create a job only for a customer request that is still outstanding. Permission on its own does not authorize a new 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 to receive outbound results. Inbound results go to the registration’s webhook.
The response is HTTP 202 with the call object, including id (call_...) and status. An accepted request does not mean the customer answered. Voice Infra handles the SIP signaling and audio, so 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.
  • Keep completed jobs for longer than our 24-hour call-idempotency window, and stop automatic retries before the window expires so that an old webhook cannot start a new call.
  • 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. This signature is separate from Meta’s webhook signature and from your API keys. The same Python verifier 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 tells you which brand owns the call. For inbound calls, match the business number and agent against your stored brand mapping. Keep historical mappings so that late events still match. Do not assign a call to a brand from client-provided metadata alone. Handle call.started, call.completed, call.failed, call.aborted and, where applicable, call.voicemail. If post-call processing is configured, you may also receive call.processed. Use data.call.status and ended_reason to tell outcomes apart. 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. The transcript, recording and analysis can become available some time after the call ends.

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. Match settled call costs to brands through your stored brand mapping, and use those records for your own resale billing. Voice Infra does not create separate invoices or balances for brands inside this shared workspace. Confirm the workspace’s current pricing and wallet terms. Do not assume a pilot charge applies to everyone. Meta charges are separate. Coordinate changes to keys and the workspace’s pinned inbound calling configuration with Voice Infra before changing production billing assumptions. If the shared balance runs out, every brand can be affected, so monitor it centrally. Per-brand dashboards and fair allocation are up to your application. This shared-workspace recipe does not provide strict per-brand inbound spend limits, and the optional resolver cannot serve as a reliable hard budget limit.

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 show how many simultaneous calls your customers can make. Measure expected calls per second, concurrent calls and average call duration with Voice Infra before you agree on 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, to change where results go, send the version from your latest read of the number:
DELETE blocks new calls immediately and lets existing calls finish. Keep the call and billing history. DELETE is not a temporary pause, and there is no public API to re-enable the registration, so 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. Listen for the greeting, say a short sentence, and confirm the agent repeats its meaning. Do this more than once 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, any available transcript or 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 that suit your use case. Meta calling permission does not give you consent to record. Work out which privacy and calling requirements apply to your operation, including GDPR, HIPAA or TCPA where relevant. Following this recipe does not make you compliant or authorize you to handle 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 every brand just because the first number registered. Keep each brand in setup until its real call acceptance tests pass, then expand in measured batches. Even when every API request succeeds, you still need number approval, partner production activation and reliable audio.

References

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