curl -X POST https://tawked.com/v1/whatsapp/messages \
-H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-30517-received" \
-d '{
"to": "0550000000",
"template": "order_received",
"lang": "ar",
"params": {
"customer_name": "سارة",
"order_number": "30517"
},
"reference": "order-30517"
}'const res = await fetch("https://tawked.com/v1/whatsapp/messages", {
method: "POST",
headers: {
Authorization: "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
"Idempotency-Key": "order-30517-received",
},
body: JSON.stringify({
"to": "0550000000",
"template": "order_received",
"lang": "ar",
"params": {
"customer_name": "سارة",
"order_number": "30517"
},
"reference": "order-30517"
}),
});
const data = await res.json();<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://tawked.com/v1/whatsapp/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => '<string>',
'template' => '<string>',
'lang' => '<string>',
'params' => [
],
'header' => [
],
'buttons' => '<array>',
'reference' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}import requests
res = requests.post(
"https://tawked.com/v1/whatsapp/messages",
headers={"Authorization": "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx", "Idempotency-Key": "order-30517-received"},
json={
"to": "0550000000",
"template": "order_received",
"lang": "ar",
"params": {
"customer_name": "سارة",
"order_number": "30517"
},
"reference": "order-30517"
},
)
data = res.json(){
"id": "0f4c9c0e-6d2b-4b8a-9c3e-7a1d2e3f4a5b",
"status": "accepted",
"to": "+966550000000",
"template": "order_received",
"lang": "ar",
"reference": "order-30517",
"price_halalas": 0,
"created_at": "2026-09-06T12:00:00+03:00"
}{
"error": "service_required"
}{
"error": "unauthorized"
}{
"error": "insufficient_credits",
"message": "Insufficient credits",
"balance_halalas": 0,
"price_halalas": 9
}{
"error": "account_not_active"
}{
"error": "service_not_found"
}{
"error": "no_whatsapp_number"
}{
"error": "unknown_template"
}{
"error": "too_many_requests"
}{
"error": "internal_error"
}{
"error": "send_failed"
}{
"error": "templates_unavailable"
}Send a template message
Sends an approved template to a Saudi mobile through the number attached to the application, and answers 202 with an id to track it. The fee, if any, is for the send; a failed send refunds it.
curl -X POST https://tawked.com/v1/whatsapp/messages \
-H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-30517-received" \
-d '{
"to": "0550000000",
"template": "order_received",
"lang": "ar",
"params": {
"customer_name": "سارة",
"order_number": "30517"
},
"reference": "order-30517"
}'const res = await fetch("https://tawked.com/v1/whatsapp/messages", {
method: "POST",
headers: {
Authorization: "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
"Idempotency-Key": "order-30517-received",
},
body: JSON.stringify({
"to": "0550000000",
"template": "order_received",
"lang": "ar",
"params": {
"customer_name": "سارة",
"order_number": "30517"
},
"reference": "order-30517"
}),
});
const data = await res.json();<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://tawked.com/v1/whatsapp/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => '<string>',
'template' => '<string>',
'lang' => '<string>',
'params' => [
],
'header' => [
],
'buttons' => '<array>',
'reference' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}import requests
res = requests.post(
"https://tawked.com/v1/whatsapp/messages",
headers={"Authorization": "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx", "Idempotency-Key": "order-30517-received"},
json={
"to": "0550000000",
"template": "order_received",
"lang": "ar",
"params": {
"customer_name": "سارة",
"order_number": "30517"
},
"reference": "order-30517"
},
)
data = res.json(){
"id": "0f4c9c0e-6d2b-4b8a-9c3e-7a1d2e3f4a5b",
"status": "accepted",
"to": "+966550000000",
"template": "order_received",
"lang": "ar",
"reference": "order-30517",
"price_halalas": 0,
"created_at": "2026-09-06T12:00:00+03:00"
}{
"error": "service_required"
}{
"error": "unauthorized"
}{
"error": "insufficient_credits",
"message": "Insufficient credits",
"balance_halalas": 0,
"price_halalas": 9
}{
"error": "account_not_active"
}{
"error": "service_not_found"
}{
"error": "no_whatsapp_number"
}{
"error": "unknown_template"
}{
"error": "too_many_requests"
}{
"error": "internal_error"
}{
"error": "send_failed"
}{
"error": "templates_unavailable"
}Authorizations
Authorization: Bearer . An application key (tk_live_, tk_test_) has full access, which covers every product and any product added later, or custom access, chosen when the key is created: one or more of the scopes verify:check, verify:send (includes verify:check), notifications:read, notifications:send (includes notifications:read), email:read, email:send (includes email:read), email:manage (includes email:send), webhooks:manage, email:inbound, notifications:templates (includes notifications:read), chat:menus, chat:attendant. No key made on the API keys page holds notifications:templates, full access included: the three template writes answer such a key 403 insufficient_scope. A connected app's key holds it. Nor chat:menus: the five conversation menu operations answer such a key 403 insufficient_scope, and a connected app's key holds it once the client approved it. Nor chat:attendant: the five auto-attendant operations answer such a key 403 insufficient_scope too, and a connected app's key holds it once the client approved it. email:manage and webhooks:manage are held only by a key someone chose them for: a full-access key, old or new, never holds them. email:inbound, which reads, deletes and forwards received e-mail, is opt-in on every key, full access included: a key holds it only when it was turned on when the key was created. A call outside the key's access answers 403 insufficient_scope and names the scope it needs in the WWW-Authenticate response header. A partner key (tk_partner_) always has full access. Whatever its access, a key is a server-side secret.
Headers
A 1 to 128 printable-ASCII-character key you generate, scoped to your API key. Replaying the same key returns the original 202 response unchanged, with the response header Idempotent-Replayed: true: nothing is sent or charged again. A send that fails with 502 releases the key. Use one key per business event (order-1042-received) and retry a 502 or a network error with the same key.
Body
The destination: a Saudi mobile in any of the accepted formats, stored as E.164.
The template name exactly as WhatsApp Manager lists it, 1 to 512 characters. It must be approved on the number's WhatsApp Business Account.
The template language as Meta lists it (ar, en, en_US). Required when the template exists in more than one language.
Placeholder values for the body: the keys are the placeholder names of a named template ({{name}}), or "1", "2" for a positional one ({{1}}). Every placeholder is required, extra keys are refused, values are strings of 1 to 1024 characters. An authentication template takes exactly { "code": "…" }, 4 to 15 letters or digits, which fills its text and its button. GET /v1/whatsapp/templates lists the placeholders of each template.
{ "text": "…" }: the value of the text header's placeholder. Required when the template has one, refused otherwise; no newlines. For a template that opens with an image, { "image": { "link": "https://…" } }: the image's public https link, at most 2048 characters, a JPG or PNG of at most 5 MB that Meta fetches when it sends; required for such a template, refused for any other. For a template that opens with a document, { "document": { "link": "https://…/Contract-1042.pdf", "filename": "Contract-1042.pdf" } }: link is required, an https link of at most 2048 characters with no spaces to a PDF (Meta's limit for a document is 100 MB), which Meta downloads when it sends, so it must open without a sign-in at that moment (a signed link that lives a few minutes suits a file with personal data, such as an invoice or a contract); filename is optional, 1 to 240 characters with no control characters and no / or \, the name the customer sees on the document (without it WhatsApp shows a generic name). A document is required for such a template and refused for any other, and an image is refused for it. A header of another shape (document that is not an object, a link that is not a string, extra keys, text beside document) answers 400 invalid_request. Video and location headers are not supported yet.
[{ "index": 0, "parameter": "…" }]: the dynamic suffix of each URL button, one entry per button that has a placeholder; index is the button's position among the template's buttons. Refused for an authentication template: params.code fills its button.
Your own reference (order id, event id), 1 to 64 printable characters. Echoed back, and searchable in the Messages log of the console.