API & Docs
Build withTalkify API.
A focused guide from your first API key to call outcomes and webhooks.
# Your first API call — speak a line of Bengali down the phone
curl -X POST https://app.talkify.com.bd/api/v1/outbound/call \
-H "X-API-Key: $TALKIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone": "8801712345678", "text": "আপনার অর্ডারটি পাঠানো হয়েছে।"}'
# Create the key in the dashboard: Developers → API KeysBuilt for developers.
API key authentication
Create keys in the dashboard and send one as X-API-Key or a Bearer token. Each key carries its own permission scopes.
Swagger / OpenAPI
An interactive reference for every public endpoint at app.talkify.com.bd/api/docs. Try calls from the browser with your key.
Signed webhooks
Get notified when calls finish or are missed, campaigns start and end, voicemails arrive, and more — each request signed so you can verify it.
RESTful JSON API
Predictable REST endpoints with JSON bodies, readable error messages, and pagination and filters on the call list.
Rate limiting
Requests are rate limited to keep the platform responsive for every workspace.
Safe retries
Send your own externalId with a call. Sending it again returns the original call instead of dialling twice.
Quickstart
Five steps from an API key to a ringing phone. Create your key in the dashboard under Developers → API Keys, paste it in, and run.
The Interactive Calls API is included with Growth, Professional and Enterprise. Keys carry per-permission scopes — outbound:call to place calls, outbound:status to read them back.
1. Authenticate
Create a key under Developers → API Keys in the dashboard. Send it as a header on every request — either form works.
# Either header is accepted
X-API-Key: tk_live_xxxxxxxxxxxxxxxxxxxx
Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx
# Base URL
https://app.talkify.com.bd/api/v12. Place your first call
Speak a line of text to one number. Bengali works as-is — pick a voice and it is read in Bengali.
curl -X POST https://app.talkify.com.bd/api/v1/outbound/call \
-H "X-API-Key: $TALKIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "8801712345678",
"text": "আপনার অর্ডারটি পাঠানো হয়েছে। ধন্যবাদ।",
"externalId": "order-10482"
}'3. Personalise it
Send a template with variables and each recipient hears their own version. Add dtmfOptions and the call becomes a one-question survey.
curl -X POST https://app.talkify.com.bd/api/v1/outbound/call \
-H "X-API-Key: $TALKIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "8801712345678",
"template": "{{name}}, আপনার বিল {{amount}} টাকা বকেয়া আছে।",
"variables": { "name": "রহিম", "amount": "১,২৫০" },
"dtmfOptions": { "1": "এখনই পরিশোধ", "2": "পরে মনে করিয়ে দিন" },
"webhookUrl": "https://your-app.com/talkify/webhook"
}'4. Call a list
Up to a batch per request, each with its own variables. The response returns an id per call.
curl -X POST https://app.talkify.com.bd/api/v1/outbound/bulk \
-H "X-API-Key: $TALKIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"calls": [
{ "phone": "8801712345678", "text": "প্রথম বার্তা", "externalId": "a-1" },
{ "phone": "8801812345678", "text": "দ্বিতীয় বার্তা", "externalId": "a-2" }
]
}'5. Check what happened
Poll by id, or filter the list by status and your own externalId. Or skip polling and let the webhook tell you.
# One call
curl https://app.talkify.com.bd/api/v1/outbound/call/CALL_ID \
-H "X-API-Key: $TALKIFY_API_KEY"
# Everything you sent for one order
curl "https://app.talkify.com.bd/api/v1/outbound/calls?externalId=order-10482" \
-H "X-API-Key: $TALKIFY_API_KEY"In your language
No package to install — Node uses the built-in fetch, PHP uses cURL. The webhook example is the one worth reading twice: the signature covers the raw bytes, so parsing the body before verifying it makes every check fail.
Place a call
No dependencies — fetch is built in from Node 18. Reusing an externalId returns the original call instead of dialling twice.
const BASE = 'https://app.talkify.com.bd/api/v1';
async function talkify(path, options = {}) {
const res = await fetch(BASE + path, {
...options,
headers: {
'X-API-Key': process.env.TALKIFY_API_KEY,
'Content-Type': 'application/json',
...options.headers,
},
});
const body = await res.json();
if (!res.ok) {
// A validation failure returns message as an array of strings;
// an auth failure returns a single string.
const detail = Array.isArray(body.message) ? body.message.join('; ') : body.message;
throw new Error(`Talkify ${res.status}: ${detail}`);
}
return body;
}
const { data } = await talkify('/outbound/call', {
method: 'POST',
body: JSON.stringify({
phone: '8801712345678',
text: 'আপনার অর্ডারটি পাঠানো হয়েছে। ধন্যবাদ।',
externalId: 'order-10482', // your id — send it again and you get the
webhookUrl: 'https://your-app.com/talkify/webhook',
}), // same call back, never a second dial
});
console.log(data.callId, data.status); // '…-…' 'QUEUED'Check what happened
Poll one call by id, or fetch everything you sent under one of your own ids. Note the list endpoint has no success wrapper.
// One call
const { data: call } = await talkify('/outbound/call/' + callId);
console.log(call.status, call.dtmfPressed, call.duration);
// Everything for one order — this endpoint returns { data, pagination }
const { data: calls, pagination } = await talkify(
'/outbound/calls?externalId=order-10482'
);
console.log(`${calls.length} of ${pagination.total}`);Verify a webhook
Sign-and-compare on the RAW body. Parsing the JSON first and re-serialising it changes the bytes and every signature fails.
import express from 'express';
import crypto from 'node:crypto';
const app = express();
// express.raw, not express.json — the signature covers the exact bytes sent.
app.post('/talkify/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.TALKIFY_WEBHOOK_SECRET)
.update(req.body) // the Buffer, untouched
.digest('hex');
const got = req.get('X-Talkify-Signature') || '';
// Constant-time: a plain === leaks the answer one byte at a time.
const ok = got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok) return res.status(401).send('bad signature');
const event = JSON.parse(req.body.toString());
console.log(event.externalId, event.status, event.dtmfLabel);
res.sendStatus(200); // anything else and Talkify retries: 2s, 4s, 8s
});Place a call
Plain cURL, no package to install. Works on any PHP 7.4+ host, including shared hosting.
<?php
function talkify(string $path, ?array $body = null): array {
$ch = curl_init('https://app.talkify.com.bd/api/v1' . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . getenv('TALKIFY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 15,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS,
json_encode($body, JSON_UNESCAPED_UNICODE)); // keep Bengali readable
}
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$json = json_decode($raw, true);
if ($status >= 400) {
// Validation errors arrive as an array of messages, auth errors as one string.
$msg = $json['message'] ?? 'unknown error';
throw new RuntimeException("Talkify $status: " .
(is_array($msg) ? implode('; ', $msg) : $msg));
}
return $json;
}
$res = talkify('/outbound/call', [
'phone' => '8801712345678',
'text' => 'আপনার অর্ডারটি পাঠানো হয়েছে। ধন্যবাদ।',
'externalId' => 'order-10482',
'webhookUrl' => 'https://your-app.com/talkify/webhook',
]);
echo $res['data']['callId'], ' ', $res['data']['status']; // '…' 'QUEUED'Check what happened
The detail endpoint wraps its reply in success/data; the list endpoint does not. Read each as it comes.
<?php
// One call
$call = talkify('/outbound/call/' . $callId)['data'];
echo $call['status'], ' ', $call['dtmfPressed'] ?? '-';
// Everything for one order — { data, pagination }, no success wrapper
$list = talkify('/outbound/calls?externalId=order-10482');
printf("%d of %d\n", count($list['data']), $list['pagination']['total']);Verify a webhook
php://input, never $_POST. The signature covers the exact bytes, so anything that re-encodes the body breaks it.
<?php
$raw = file_get_contents('php://input'); // the bytes as sent
$got = $_SERVER['HTTP_X_TALKIFY_SIGNATURE'] ?? '';
$expect = 'sha256=' . hash_hmac('sha256', $raw, getenv('TALKIFY_WEBHOOK_SECRET'));
// hash_equals is constant-time; == would leak the signature a byte at a time.
if (!hash_equals($expect, $got)) {
http_response_code(401);
exit('bad signature');
}
$event = json_decode($raw, true);
// call.completed — status, dtmfPressed, dtmfLabel, duration, hangupCause
error_log($event['externalId'] . ' ' . $event['status']);
http_response_code(200); // anything else and Talkify retries: 2s, 4s, 8sAPI endpoints
Everything an API key opens, in full.
- POST
/api/v1/outbound/callPlace one call — play audio, speak text, or hand it to an AI agent
- POST
/api/v1/outbound/bulkPlace a batch of calls from a JSON list of numbers
- POST
/api/v1/outbound/bulk-filePlace a batch of calls from an uploaded CSV
- GET
/api/v1/outbound/call/:idStatus, duration and outcome of one call
- GET
/api/v1/outbound/callsList calls placed through the API, with filters
Webhook events
Subscribe to these events and react to platform activity.
call.completedA call finished — direction, duration, billsec and outcome
call.missedAn inbound call was not answered
campaign.startedA broadcasting campaign began dialling
campaign.completedA broadcasting campaign finished
voicemail.newA voicemail was recorded and stored
contact.createdA contact was added, by hand or by import
payment.receivedAn invoice was settled
subscription.expiringA subscription period is about to end