Developers
Developer documentation
Send WhatsApp messages, place voice calls, have an AI voice agent call your customers, verify customers over a call and receive signed events, all from your own code.
- WhatsApp base URL
- https://app.agentive.co.in/api/v1
- Voice base URL
- https://voice.agentive.co.in/api/public/v1
- Webhook payload version
- 2026-06-01
Products
Every reference has authentication, request and response shapes, error codes and copy-ready samples.
WhatsApp Business API
Send approved WhatsApp template messages with media and buttons from your own systems, tag contacts, and get delivery status on a webhook.
- Base URL
- https://app.agentive.co.in/api/v1
Voice Broadcast API
Place recorded voice calls, trigger a saved campaign for one number, poll call status, list calls and download recordings.
- Base URL
- https://voice.agentive.co.in/api/public/v1
AI Voice Agent API
Have your AI voice agent call a customer from your own code, pass the details it should use, and get the transcript, analysis and recording back.
- Base URL
- https://voice.agentive.co.in/api/public/v1
Voice OTP API
Read a one-time code out to a customer over a phone call and verify it, with expiry and attempt limits handled for you.
- Base URL
- https://voice.agentive.co.in/api/public/v1
Telephony Webhooks
Signed call, recording and verification events delivered to your endpoint the moment they happen, with HMAC verification and retries.
- Payload version
- 2026-06-01
Getting started
Three steps from a new account to a live request.
- 1
Create an account
Sign up for Agentive. The WhatsApp API is issued from the Chat dashboard and the voice APIs from the Voice dashboard, so start with the product you are integrating.
- 2
Get your credentials
Generate credentials in the dashboard. Each reference explains where to find them and which headers to send on every request.
- 3
Make your first request
Copy the quick start for your product, replace the placeholders, and send one message or place one call to your own number.
Platform conventions
Agentive ships two API families: the Voice APIs (Voice Broadcast, AI Voice Agent, Voice OTP and telephony webhooks) and the WhatsApp Business API. They share a brand, a dashboard account and a set of habits, but they are separate products and they do not agree on everything. This section is the one place that says what is common and, where they differ, exactly how.
Base URLs
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Base URL | https://voice.agentive.co.in/api/public/v1 | https://app.agentive.co.in/api/v1 |
| Covers | Voice Broadcast, AI Voice Agent, Voice OTP and the endpoints webhook events point back at. All of them share one base, one credential pair and one envelope. | Template sends for one API campaign. Issued and managed from the Chat dashboard. |
| Transport | HTTPS only, JSON in and JSON out. | HTTPS only, JSON in and JSON out. |
Authentication
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Credential | One publishable key (pk_live_...) and one secret (sk_live_...) per account. | One token (agcamp_...) per API campaign, so a leaked token can only send that campaign. |
| How to send it | x-api-key and x-api-secret headers, or HTTP Basic with the publishable key as the username and the secret as the password. | Authorization: Bearer <token>. |
| Scope | Account-wide. Every endpoint on the Voice base accepts it. | Campaign-wide. The template and number are fixed on the campaign. |
| Treat as | Both keys are confidential. The publishable key names your account to anyone who holds it, so keep it with the secret, on your server only. | A secret. Keep it on your server only. |
| Rotation | From the API Access tab of the Voice dashboard. The old secret stops working at once. | From the campaign page in the Chat dashboard. The old token stops working at once. |
Error envelope
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Shape | { status, code: number, error_code: string, error, details, unique_id, reference_id } | { error, code: string, details: object } |
| What to branch on | error_code. The numeric code mirrors the HTTP status and stays for compatibility. | code, which is itself the machine string. |
| Human sentence | details, repeated in error so a handler written for the WhatsApp shape reads a Voice error too. | error. |
| Extra context | Named fields beside the envelope (lines, lines_in_use, attempts_left, retry_after_sec, balance_inr). | A details object (missing, retryAfter, meta, validation). |
401 and 403
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| 401 | Credentials missing, unknown, or rejected. The status never confirms whether a key exists. | Token missing or wrong. |
| 403 | Credentials accepted, but the account is not active or the product is not enabled. | Token accepted, but the campaign is not live or the number is not connected. |
| 404 | Not found, or not yours. A foreign id is never a 403, because that would confirm it exists. | Not found, or not yours. Same rule. |
Idempotency
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Key | Idempotency-Key request header, 1 to 128 visible characters. A longer key is refused with HTTP 400. | externalId in the body (the Idempotency-Key header is accepted too), up to 160 characters. |
| Window | 24 hours per account. | The life of the campaign. |
| A repeat | Replays the stored response. The one exception is a 5xx raised after the call was already handed to the network: that reply is stored as call_state_unknown with a sentence appended telling you to check the call before retrying. A 400 or 404 is stored for the 24 hours too, so once you fix the request or the dashboard setting, send it with a new key. | HTTP 200 with duplicate: true and the earlier delivery's current status. |
| Replay marker | Idempotent-Replayed: true, plus the original idempotency-replayed: true header. | duplicate: true in the body. |
| Same key, different body | HTTP 422 with idempotency_conflict. | The first send stands; the second is reported as a duplicate. |
| Still in flight | HTTP 409 with idempotency_in_progress and Retry-After. | Not applicable. |
Rate limits and headers
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Budget | Per account: 120 reads a minute and 120 writes a minute, counted separately, so polling can never starve call placement. | Per campaign token: 300 requests a minute over a sliding window. |
| Limit headers | RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, with X-RateLimit-* aliases of the same three values. | None. |
| How long to wait | Retry-After on every 429 and every 409, mirrored in the body as retry_after_sec. | Retry-After on rate_limited; details.retryAfter (IST) on quiet_hours. |
| Capacity refusal | HTTP 429 concurrency_limit when the account has no free line, with lines, lines_in_use and retry_after_sec in the body and a Retry-After of 5 seconds. Call-placing responses also carry X-Agentive-Lines and X-Agentive-Lines-In-Use. | Not applicable; there are no lines to exhaust. |
Webhook signing
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Header to verify | X-Agentive-Signature-V2: t=<unix seconds>,v2=<hex> | X-Agentive-Signature: <hex>, with no prefix. |
| What is signed | HMAC-SHA256, with the signing secret, of t, a dot, then the exact raw body. | HMAC-SHA256 of the exact raw body with the endpoint's signing secret. |
| Freshness | Reject a t more than 300 seconds from your own clock. The timestamp is inside the signed material, so a captured delivery cannot be sent again later. | Nothing time-bound is signed. De-duplicate on the event id for good, and accept deliveries over HTTPS only. |
| Comparison | Constant time, on bytes. Reject on any mismatch, and reject a delivery with no version 2 header. | Constant time, on bytes. Reject on any mismatch. |
X-Agentive-Signature-V2. Voice deliveries also carry the older body-only X-Agentive-Signature for receivers built before version 2; do not accept it on its own, because it proves nothing about when a body was sent. The WhatsApp signature is the body alone with no prefix. A shared handler needs one verifier per product.Webhook delivery headers
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Event name | X-Agentive-Event | X-Agentive-Event |
| Event id | X-Agentive-Event-Id, the same value as id in the body. Shared by every endpoint that receives the event. | X-Agentive-Delivery, the same value as id in the body. Despite the name it is the event id, not a per-endpoint delivery id. |
| Delivery id | X-Agentive-Delivery-Id (dlv_...), one per event per endpoint, stable across every retry. | None. X-Agentive-Delivery is the Chat product's name for the event id above. |
| Attempt number | X-Agentive-Attempt, 1-based, counting through the whole retry schedule. | None. |
| Attempt time | X-Agentive-Timestamp, Unix seconds at the moment of this attempt, the same value as t in the version 2 signature. Check the age against the signed t, never against this header alone. | X-Agentive-Timestamp, the same timestamp as the envelope. |
| User agent | Agentive-Webhooks/2026-06-01 | Agentive-Webhooks/2026-05-08, the Chat payload version. |
Webhook retries
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Attempts | Nine: three fast, then six spread out. | Three. |
| Schedule | 0.5 s and 2 s between the first three, then 1 min, 5 min, 30 min, 2 h, 6 h and 12 h. | 0 s, 2 s and 8 s. |
| Total window | About 24 hours, after which the delivery is marked failed. | About 10 seconds. |
| Timeout per attempt | 6 seconds. | 10 seconds. |
| What you see | The dashboard lists recent deliveries per endpoint with the event, the latest result, the number of tries and the time in IST. | The campaign's Deliveries tab shows the message and its current status. |
Timestamps and amounts
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| Time zone | Indian Standard Time, ISO 8601 with a +05:30 offset. The field is named per product: created_at_ist on a call, expires_at_ist on a verification, timestamp_ist on a webhook envelope. | timestamp on the webhook envelope is UTC ISO 8601. Schedules and quiet hours are set and reported in IST. |
| UTC alongside | created_at_utc on a call, and occurred_at (UTC) plus created (Unix seconds) on a webhook envelope. | The envelope timestamp is already UTC. |
| Money | Indian Rupees, always. balance_inr is a number, never a formatted string. | Indian Rupees, always. Amounts appear in the dashboard, not in the send API. |
Versioning and field case
| Voice APIs | WhatsApp Business API | |
|---|---|---|
| API version | In the path: /api/public/v1. | In the path: /api/v1. |
| Payload version | api_version on every webhook event. Currently 2026-06-01. | apiVersion on every webhook event. Currently 2026-05-08. |
| Compatibility | Fields are added, never removed or redefined. Ignore fields you do not recognise. | Fields are added, never removed or redefined. Ignore fields you do not recognise. |
| Field case | snake_case throughout, request and response. | camelCase throughout, request and response. |
Security
- Every credential is a secret. That includes the Voice publishable key, whatever its name suggests: treat it exactly like the secret. Keep all keys on your server, in an environment variable or a secrets store, never in a browser, a mobile app, a spreadsheet or source control.
- Send them only to the API. Never forward a key or a secret to any other host, and do not let an HTTP client carry them across a redirect to another host.
- Rotate at once if one leaks. Rotate the Voice pair from the API Access tab of the Voice dashboard and a WhatsApp token from its campaign page. The old value stops working immediately, so update your server in the same step.
- HTTPS only. Write
https://in the base URL in your code. A request sent over plain HTTP has already exposed its credentials before any redirect can protect it. - No address allow-listing today. Requests are not restricted to your server's IP addresses, and webhooks are not sent from a fixed list of addresses. Authenticate every webhook by its signature, never by where it came from.
Changelog
Changes that could affect code you have already written. Everything not listed here is additive: new fields, new headers and new optional request fields, which a handler that ignores what it does not recognise absorbs on its own.
September 2026
| What changed | It used to | It now | What to do |
|---|---|---|---|
| Webhook verification is version 2 only (29 Sep) | The samples verified X-Agentive-Signature-V2 when present and fell back to the body-only X-Agentive-Signature when it was absent. | Every sample verifies X-Agentive-Signature-V2 only, rejects a timestamp older than five minutes, and refuses a delivery that does not carry it. Every delivery carries it. | Remove any fallback to X-Agentive-Signature from your receiver. A body-only signature can be replayed by anyone who captured one delivery, so accepting it keeps that door open. |
| Recordings are streamed (29 Sep) | GET /calls/:unique_id/recording usually answered 302 with a short-lived download link, and the samples told your client to follow it. | The API answers 200 with the audio itself (206 for a Range request) and never redirects. The samples authenticate with HTTP Basic. | Nothing breaks: a client that followed the redirect reads the audio directly now. Stop sending your secret to any host other than the API; custom headers such as x-api-secret are carried across a redirect by most HTTP clients. |
| AI voice agent API documented (29 Sep) | POST /ai/calls worked but was undocumented, and an AI voice agent API campaign could not be triggered. | A full AI Voice Agent API reference. The trigger runs AI campaigns and takes variables and metadata, and AI calls fire call.analysed. | Invalid variables are now refused with 400 validation_error naming the key, instead of being dropped. Check the rules before you send them. |
| A wrong secret is a 401 | A known publishable key sent with a wrong secret returned 403. | Returns 401, with the same "Invalid API credentials." sentence, so the status can no longer tell an attacker that a key exists. | Move any branch that read 403 as a bad secret to 401. On the Voice APIs, 401 now always means the credentials were rejected and 403 always means they were accepted without entitlement. |
| Webhook status matches the event | data.status could disagree with the event name: a call that rang unanswered or was busy sometimes arrived as failed, and a call that could not be placed sometimes arrived as missed. | data.status is derived from the event name, so call.no_answer and call.busy are always missed and call.failed is always failed. | Nothing, if you branch on the event type or on end_reason. Remove any workaround that corrected the old values. |
| Webhook User-Agent | An internal build string that did not match the Agentive-Webhooks/... form the docs promised. | Agentive-Webhooks/2026-06-01, the payload version, so it changes only when api_version does. | Update any allow-list or log filter that matched the old string. Never authenticate a delivery on the User-Agent; verify the signature. |
| unique_id on a direct AI call | Events after call.initiated for a call placed straight to an AI agent carried an internal session id in data.unique_id, not the id the placing response returned. | data.unique_id is the id you were given when you placed the call, on every event, and unique_id is the same on every read of the call. | Nothing for broadcast, Interactive Voice or verification calls; their ids are unchanged. For direct AI calls, match on the id the placing response returned, which now works throughout. |
Idempotency-Key longer than 128 characters is refused with HTTP 400 instead of being cut short. The per-address limit before authentication now counts only requests refused with 401 or 403, and failed secrets are counted per publishable key and client address, so a correct secret is always let through. Every webhook address must use https:// on port 443 or 8443, checked on every delivery, so an address saved earlier on plain http:// or another port needs changing. A custom signing secret must be at least 32 characters, and an account can register up to 10 endpoints. An account with the AI voice agent and not voice broadcast reads and receives webhooks for its AI calls only; any other account, including one with neither product, reads and receives all its calls. The number_flood and daily_cap refusals carry Retry-After and retry_after_sec.error_code beside the numeric code on every Voice error, an error key carrying the same sentence as details, Retry-After on every 429 and 409, X-RateLimit-* aliases, X-Agentive-Delivery-Id and X-Agentive-Attempt on webhook deliveries, a webhook retry tail that keeps trying for about 24 hours, created_at_ist and created_at_utc on the call object, and verification_status on verifications.Need help?
Stuck on an integration, or need something the API does not cover yet? Write to the team and an engineer will reply.