Send a voice message
POSThttps://api.9bits.net/voice/v1/voice/sendvoice
Request
Section titled “Request”| Field | Type | Required | Description |
|---|---|---|---|
to | array | ✅ | Destinations in international format. Max 1,000 unique numbers. |
media_url | string | ✅ | Publicly reachable URL of the audio to play. Fetched at dispatch time. |
from | string | — | Caller ID. Defaults to your registered one; chosen at random if you have several. |
Examples
Section titled “Examples”curl -X POST https://api.9bits.net/voice/v1/voice/sendvoice \ -H "Authorization: Bearer $NINEBITS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "to": ["2348020000000", "2348030000000"], "media_url": "https://cdn.example.com/audio/reminder.mp3", "from": "2349000000000" }'const res = await fetch('https://api.9bits.net/voice/v1/voice/sendvoice', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NINEBITS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ to: ['2348020000000', '2348030000000'], media_url: 'https://cdn.example.com/audio/reminder.mp3', }),});
const job = await res.json();console.log(job.ref_id); // correlates with the webhook eventsimport os, requests
res = requests.post( "https://api.9bits.net/voice/v1/voice/sendvoice", headers={"Authorization": f"Bearer {os.environ['NINEBITS_TOKEN']}"}, json={ "to": ["2348020000000", "2348030000000"], "media_url": "https://cdn.example.com/audio/reminder.mp3", }, timeout=10,)res.raise_for_status()print(res.json()["ref_id"])Response
Section titled “Response”{ "status_code": 200, "message": "Request is being processed", "ref_id": "9azmf1ntcl358gczduzz4rs0u"}ref_id is the only thing worth keeping. It’s what correlates this request
with the call events we push to your webhook. Store it against whatever business
record triggered the call.
Status codes
Section titled “Status codes”| Code | Meaning |
|---|---|
200 | Request accepted |
201 | Resource created |
401 | Authentication failed |
405 | Method not allowed |
429 | Rate limited |
500 | Server error |
{ "status_code": 401, "message": "Authentication failed: Signature verification failed"}Call results
Section titled “Call results”Every call attempt produces an event, pushed to the URL you set under Settings → Webhook. This is the only way to get call outcomes — there’s no polling endpoint.
{ "start_at": "2026-07-14T18:48:14+01:00", "answer_at": "2026-07-14T18:48:21+01:00", "end_at": "2026-07-14T18:48:53+01:00", "total_duration": 39, "call_duration": 32, "call_direction": "outbound", "from_number": "2349000000000", "to_number": "2348020000000", "total_amount": "48.00"}| Field | Meaning |
|---|---|
start_at | When we placed the call. |
answer_at | When it was answered. Absent if never answered — that’s how you detect a missed call. |
end_at | When it ended. |
total_duration | Wall-clock seconds, including ring time. |
call_duration | Billed seconds — connected time only. |
call_direction | outbound or inbound. |
from_number | Caller ID used. |
to_number | Destination. |
total_amount | Amount charged. |
app.post('/webhooks/9bits/voice', async (req, res) => { const evt = req.body; res.sendStatus(200); // acknowledge first
const answered = Boolean(evt.answer_at);
await db.query( `INSERT INTO voice_calls (to_number, answered, billed_seconds, amount, ended_at) VALUES ($1, $2, $3, $4, $5)`, [evt.to_number, answered, evt.call_duration, evt.total_amount, evt.end_at], );
if (!answered) await scheduleRetry(evt.to_number);});See the Webhooks guide for retries, acknowledgement, and de-duplication.
- Host it at a stable, public HTTPS URL. We fetch at dispatch, not at request time.
- We transcode to the best format for GSM voice — you don’t need to pre-convert.
- Keep it short. People hang up; every extra second is billed on the calls that don’t.
No idempotency key
Section titled “No idempotency key”POST /voice/v1/voice/sendvoice does not support Idempotency-Key. A retry after a
timeout can place duplicate calls.
Guard it yourself: write an “attempted” row keyed on your own business ID before calling, and check it before retrying. See Idempotency & retries.