sk_live_ key, a phone number you are allowed to call, and
(for step 5) a public HTTPS URL. That is all.
Your key arrives from your Mirai contact, in the same message as this link —
or you mint it yourself in the console
under Developers → Create key. It looks like
sk_live_ followed by 32 hex
characters and it is shown once: we store only its SHA-256, so a lost key is
rotated, never recovered. Put it in your secret manager now, before you go
further.Issued alongside it is a webhook signing secret, whsec_…. That one belongs to
the workspace, not the key — rotating a key does not change it, so your
receiver keeps verifying without an outage.1
Check the key (30 seconds)
200 OK
401 here is almost always a trailing newline from cat-ing a key file.
Paste the key; do not pipe it.That balance is real money and it is what pays for the calls below. Billing is
per minute, rounded up, one-minute minimum, at your key’s
tier — t3 at ₹3/minute on a partner key.2
Set up Postman (2 minutes, optional but worth it)
Download both files:
Postman → Import → drop in both → select the Voice Infra — Sandbox
environment → fill in
api_key (your sk_live_…) and test_phone (a number
you own, E.164). Then open 0-Quickstart and run it top to bottom: each
step saves the id the next one needs, so there is nothing to copy and paste.You can skip this entirely and use the curl below. The collection is the
faster path if more than one person on your side will touch the API.3
Create an agent (2 minutes)
An agent is the reusable configuration a call runs: prompt, opening line, voice,
language, limits. Make it once, call it thousands of times.Keep that
201 Created
id.{{order_id}} and {{customer_name}} are agent placeholders, filled per
call from variables. They are not shell or Postman variables — leave them
exactly as they are.4
Place one real call (1 minute)
Put your own number in 96 seconds is 2 billed minutes — ₹6 at
to for this one. It will actually ring.202 Accepted
202 means accepted, not connected — the phone has not rung yet. Read the
outcome when it ends:200 OK
t3. See
what a minute costs.5
Receive the webhook (3 minutes)
Poll if you must; subscribe if you can. We The events:
POST a signed JSON event at each
lifecycle transition, and you verify the signature before you trust the body.v1 is HMAC-SHA256(whsec, "{t}.{raw_body}"), hex. Sign the raw bytes — a
framework that parsed and re-serialized the JSON produces a different string and
will never verify.call.started, call.completed, call.voicemail, call.failed,
call.aborted, plus campaign.started / paused / resumed / completed /
stopped. Dedupe on id — a retried delivery repeats it. Full reference and a
test vector in Webhooks.6
Run a campaign (2 minutes)
One call proves the plumbing. A campaign is the product: upload a list, set a
window and a budget, and let the platform dial it — pacing, retries, do-not-call
suppression and all.Note the shape: the campaign object itself, with the upload result as top-level
keys. Bad rows come back by index rather than failing the whole upload.Start it:
201 Created
202 Accepted
play means the dialler is running. Outside 10:00–19:00 it waits, in play,
until the window opens — that is correct, not stuck. Full semantics in
Campaigns.7
Read the report (1 minute)
200 OK
Before you go live
Verify webhook signatures — and reject what fails
Verify webhook signatures — and reject what fails
Verify
X-Mirai-Signature over the raw body, enforce the 5-minute timestamp
window, and return 401 when it does not match. Dedupe on the event id;
retries repeat it. Do not order your state machine by arrival order — a retried
call.started can land after call.completed. Order by data.call.status.
Reference.Wire the do-not-call list to your opt-outs
Wire the do-not-call list to your opt-outs
Every “stop calling me” — on a call, over WhatsApp, by email — should become a
POST /v2/dnc in the same minute. It is idempotent, so there is no read-modify-
write. Suppression is checked at dial time, so it takes effect on campaigns that
are already running. Reference.Cap every campaign with budget_paise
Cap every campaign with budget_paise
budget_paise bounds a mistake in the list — a duplicated CSV, a column
shifted by one — which is the failure mode that no amount of code review
catches. When the cap is hit the campaign pauses with
pause_reason: "budget_exhausted", keeps its place, and continues when you raise
it. Alert on campaign.paused.Alert on wallet balance, hourly
Alert on wallet balance, hourly
GET /v2/wallet at a threshold covering a day of traffic. A 402 mid-campaign
is an expensive way to find out you are empty — and an underfunded campaign
starts, dials what it can, and then pauses itself on insufficient_balance.
Top-ups are not self-serve yet: ask your Mirai contact.Know the three limits that will actually bite you
Know the three limits that will actually bite you
Concurrency is a pacer, not a gate. Higher ceilings are a provisioning
change, not a code change — ask before the campaign, not during it.
Limits.
Handle these errors specifically
Handle these errors specifically
Branch on status first, then on
error.code. Full table and a retry wrapper in
two languages: Errors.Get the compliance basics right
Get the compliance basics right
The calling window, consent records, national DND scrubbing and telling people
they are speaking to an AI are obligations of the business placing the calls.
Campaigns enforce your window and your suppression list — they do not
make the calls compliant for you. Put the AI disclosure in
first_message; it
costs one clause and it reduces early hang-ups.
India calling rules.Where to go next
Campaigns
Windows, retries, budgets, per-contact reporting. Reference.
Agents
Prompts, voices, voicemail behaviour, duration caps. Reference.
Webhooks
Every event, the signature scheme, retries. Guide.
Billing & tiers
What a minute costs and what each tier can do. Reference.
call_id or campaign_id and we can see exactly what happened.