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.
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 andjq, 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:VOICE_BASE_URL, VOICE_API_KEY and VOICE_WORKSPACE_ID from your backend
configuration or secret manager. Confirm access:
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: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 voiceneha,
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:
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.
- cURL
- Python
- Node.js
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. LoadMETA_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:
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: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. SetVOICE_WEBHOOK_URL to your real
public HTTPS call-result receiver, implemented in step 7.
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:
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 noPOST /v2/calls request from your application:
- The customer taps the voice-call button in the brand’s WhatsApp chat.
- Meta delivers the call to Voice Infra.
- The registered agent answers and conducts the conversation.
- Events go to the number registration’s
webhook_url, when configured.
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
Setresolver_url on the registration if your backend should supply context
before the agent answers. We send a signed request with this shape:
X-Mirai-Signature over the raw bytes as described in step 7. Identify
the brand from the registered business number and return HTTP 200:
{"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:
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.
- cURL
- Python
- Node.js
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 anddata.call:
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:
(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. UseGET /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 acompleted 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:
Test it
Use the 120-second test agent from step 2 on one approved number. Record a call ID for every test:- Call the number in WhatsApp. Hear the greeting, say a short sentence, and confirm the agent repeats its meaning. Repeat to catch intermittent failures.
- Make a genuine callback request through your app, accept native Meta calling permission, and confirm your job worker starts one call with audible speech.
- Replay the permission webhook. Confirm it reuses the job and does not ring the customer again.
- Test missing or revoked permission, a failed call, and a duplicate result webhook. Confirm your app records the correct outcome.
- Check signed result delivery, available transcript/recording and settled usage.
- 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
- Agents, calls, webhooks and wallet.
- Meta SIP configuration.
- Meta call settings.
- Meta calling permissions.