With the TurkeySMS API you can send SMS and OTP messages from your application, schedule sends, manage your contacts, query your balance and Sender IDs, and retrieve send reports. All requests are sent over HTTPS with the POST method, and responses are in JSON.
The information on this page is based on how the API behaves in the live system. The sections follow the layout of API Center in the panel.
| Section | Endpoints |
|---|---|
| Key check | /auth/post/check/ |
| Messaging | /sms/send, /group/send, /group/sendMixed, /otp/send, /otp/detailed |
| Contacts | /groups/create, /groups/edit, /groups/delete, /groups/list, /contacts/add, /blacklist/post/add, /blacklist/post/status |
| Queries and reports | /balance/, /senderid/check, /sms/status, /reports/basic, /reports/detailed |
| Webhook | Event notifications are sent to your server |
TS-1030 on most endpoints).title field. You can list your approved Sender IDs with Sender ID query.sms_lang as 1.curl -X POST https://api.turkeysms.com.tr/sms/send \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": "905XXXXXXXXX", "text": "Siparişiniz kargoya verildi.", "sms_lang": 1 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'title' => 'BASLIGINIZ', 'sentto' => '905XXXXXXXXX', 'text' => 'Siparişiniz kargoya verildi.', 'sms_lang' => 1, ]; $ch = curl_init('https://api.turkeysms.com.tr/sms/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": "905XXXXXXXXX", "text": "Siparişiniz kargoya verildi.", "sms_lang": 1, } r = requests.post("https://api.turkeysms.com.tr/sms/send", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', title: 'BASLIGINIZ', sentto: '905XXXXXXXXX', text: 'Siparişiniz kargoya verildi.', sms_lang: 1, }; const r = await fetch('https://api.turkeysms.com.tr/sms/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
In a successful request, result is true and result_code is TS-1024. For details, see the Sending SMS section.
https://api.turkeysms.com.tr. Endpoint paths are appended to this URL (for example, https://api.turkeysms.com.tr/sms/send). The URL has no version prefix.POST only.Content-Type: application/json, UTF-8). Form data is also accepted; the examples use JSON. The query string is not read./balance/ and /auth/post/check/ are written with a trailing slash; /senderid/check and the others are written without one. A wrong path returns a redirect (301) or 404.Send your API key in the api_key field of the body with every request. A key sent in an HTTP header (for example, Authorization) is not read.
Most endpoints use the envelope below. In successful responses, endpoint-specific fields are added to the same object.
{ "result": false, "result_code": "TS-1031", "result_message": "Invalid API key. Authentication failed." }
The Reports and Number blocking endpoints use the status field ("success" or "error") instead of result.
result (or status) and result_code fields. result_message texts are in English and may change; use result_code in your program.On an unexpected server error, the response is usually HTTP 500 with result_code SRV-ERR (TS-5000 in Key check). Rarely, the response may not be JSON, or may contain an error field instead of result_code; also catch JSON parsing errors in your code. You can retry the request after a short while; on send endpoints, before retrying, check whether the message went out with SMS status query or Reports.
A per-minute send limit may apply to /sms/send at the account level. This limit is set on your account by TurkeySMS. The limit counts the messages (recipients) recorded from your account in the last minute, not the number of requests. When the limit is reached, the response is HTTP 429 with TS-1060; wait a while and retry.
Dates use the YYYY-MM-DD format, and times use HH:MM:SS (hour:minute:second) or, in some fields, HH:MM. Date and time values returned in responses carry no time zone information.
API keys are managed in the panel under API Center → My Keys. Each key has its own permissions; we recommend creating separate keys for your different systems and giving each one only the permissions it needs.
In the panel, keys appear as “Active”, “Paused”, “Expired” or “Revoked”. Only keys in the Active state can make requests; requests made with a key shown in any other state in the panel are rejected with TS-1031 or TS-1035. If an expiry date is set for the key, requests made after the date passes are rejected with TS-1035. You can reactivate a paused key; a revoked key cannot be restored.
In the key details, Danger Zone → Rotate key creates a new key; the permissions and settings are moved to the new key. If you tick the “Keep the old key working for 24 more hours” option, the old key stays valid for 24 more hours; during this time you can move your systems to the new key. If you do not tick the option, the old key becomes invalid immediately. After the period ends, requests made with the old key are rejected with TS-1035 or TS-1031. If the key has leaked, do not tick the option.
Permissions are turned on and off in the Scopes tab of the key details. The table below shows the endpoints where each permission is checked and the code returned when the permission is off. “Allow POST requests” is not a sending method but a separate permission; it is checked only on the endpoints listed.
| Permission (panel) | Endpoints | If off |
|---|---|---|
| Allow POST requests | /sms/send, /sms/status, /otp/detailed, /reports/basic, /reports/detailed | TS-1061 |
| Send SMS | /sms/send | TS-1062 |
| Send to group | /group/send, /group/sendMixed | TS-1067 |
| Send OTP | /otp/send | TS-1036 |
| Advanced OTP | /otp/detailed | TS-1037 |
| Create group | /groups/create | TS-1081 |
| Edit group | /groups/edit | TS-1084 |
| Delete group | /groups/delete | TS-1085 |
| List groups | /groups/list | TS-1089 |
| Add number | /contacts/add | TS-1065 |
| Block number | /blacklist/post/add, /blacklist/post/status | TS-1065 |
| Check balance | /balance/ | TS-1065 |
| Check SMS status | /sms/status, /reports/basic, /reports/detailed | TS-1063 |
| Check Sender ID | /senderid/check | TS-1038 |
/auth/post/check/ requires no permission. You can use this endpoint to query a key's permissions.
In the “Limits” step of the wizard and in the “Limits” and “Security” tabs of the key details, you can set hourly, daily and monthly limits, an IP allowlist and an expiry date for a key. These rules apply only when they are set in the API Center. A limit left empty or set to 0 means no limit; an empty allowlist means no IP restriction.
| Rule | When the request is rejected | Code (HTTP) |
|---|---|---|
| Hourly limit | When the number of requests made with the key within the current hour (for example 14:00–14:59) reaches the limit. | TS-1068 (429) |
| Daily limit | When the number of requests made with the key on the current day reaches the limit. | TS-1069 (429) |
| Monthly limit | When the number of requests made with the key in the current calendar month reaches the limit. | TS-1073 (429) |
| IP allowlist | When the IP address the request comes from is not on the list. If the key has its own list, only that list is used; otherwise the account's list is used. The list can contain single IP addresses or CIDR ranges (for example 203.0.113.0/24). | TS-1066 (403) |
| Expiry date | After the date passes. The key is valid until the end of the set day. | TS-1035 (403) |
TS-1060) that TurkeySMS sets on your account.Returns whether your API key is valid, its permissions and an account summary. You can use it to check the permissions before taking an integration live.
Required permission: None. An active API key is enough.
/auth/post/check/. The old /auth/check URL is not used (404).| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. 20–128 characters. |
curl -X POST https://api.turkeysms.com.tr/auth/post/check/ \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', ]; $ch = curl_init('https://api.turkeysms.com.tr/auth/post/check/'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", } r = requests.post("https://api.turkeysms.com.tr/auth/post/check/", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', }; const r = await fetch('https://api.turkeysms.com.tr/auth/post/check/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1000", "result_message": "Authentication audit successful", "key_details": { "status": "Active", "permissions": { "post_request": true, "send_single_sms": true, "send_otp": true, "check_balance": true, "check_senderid": true, "manage_groups": false, "send_group_sms": true, "send_otp_advanced": false, "create_group": false, "edit_group": false, "delete_group": false, "list_groups": false, "add_contact": false, "delete_contact": false, "block_number": false, "check_sms_status": true } }, "account_summary": { "account_status": "Active", "balance": { "main": 1500, "international": 0 }, "global_sending": false }, "audit_info": { "request_ip": "203.0.113.10", "checked_at": "2026-10-01 14:30:00" } }
| Field | Description |
|---|---|
key_details.status | The key's state. Because only active keys get a response, it is Active in a successful response. |
key_details.permissions | 16 permission fields (true/false). Their panel equivalents are in the table below. |
account_summary.account_status | The account's state. It is Active in a successful response. |
account_summary.balance.main | The SMS credit in your account (integer). |
account_summary.balance.international, account_summary.global_sending | Additional fields. |
audit_info.request_ip | The IP address the request came from. |
audit_info.checked_at | The date and time of the check. |
Panel equivalents of the permission fields:
| Field | Panel permission |
|---|---|
post_request | Allow POST requests |
send_single_sms | Send SMS |
send_group_sms | Send to group |
send_otp | Send OTP |
send_otp_advanced | Advanced OTP |
create_group | Create group |
manage_groups | Create group (old name; carries the same value as create_group and is kept for backward compatibility) |
edit_group | Edit group |
delete_group | Delete group |
list_groups | List groups |
add_contact | Add number |
delete_contact | Has no equivalent in the panel; not used by the endpoints documented on this page. |
block_number | Block number |
check_balance | Check balance |
check_sms_status | Check SMS status |
check_senderid | Check Sender ID |
{ "result": false, "result_code": "TS-1031", "result_message": "Invalid API key" }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1031 | 401 | api_key is missing, is not a string or is outside 20–128 characters. |
| TS-1031 | 400 | The key was not found or is not in the “Active” state. |
| TS-1030 | 400 | The account is not active. |
| TS-5000 | 500 | Unexpected server error. |
Sends the same text to one or more numbers. When the request succeeds, the messages are accepted for delivery to the operator.
Required permissions: “Allow POST requests” and “Send SMS” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| sentto | string | Required | Recipient number. Separate multiple numbers with commas, semicolons or line breaks. At most 500 distinct numbers per request (50,000 for scheduled sends). If a number appears more than once, it receives the message once. |
| title | string | Required | A Sender ID approved on your account. At most 11 characters; Turkish characters (ç, ğ, ı, ö, ş, ü) count as two characters. |
| text | string | Required | Message text. At most 2,000 characters. The TS-L token in the text is converted to a line break. |
| sms_lang | int | Optional | Character set and SMS count calculation: 0 English, 1 Turkish, 2 Arabic/Unicode. Default 2. For Turkish text, send 1 (see Message language and SMS count). |
| content_type | int | Optional | Content label: 0 Transactional, 1 High Quality, 2 Advertising. Default 0. Returned only as a label in the response; it does not affect the send. |
| scheduled_date | string | Optional | If set, the send is scheduled. See Scheduled sending. |
| scheduled_time | string | Conditional | Required when scheduled_date is sent. |
Send numbers in international format, without a leading +: 905XXXXXXXXX. The API removes spaces, +, - and parentheses; removes the 00 from numbers that start with 00; and converts 05… and 10-digit 5… numbers to the 905… format. Numbers are not validated one by one; a wrong number is also accepted and added to the SMS count. Validate numbers on your side before sending.
TS-1027).curl -X POST https://api.turkeysms.com.tr/sms/send \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": "905XXXXXXXXX,905YYYYYYYYY", "text": "Randevunuz yarın 10:00'\''da.TS-LBilgi için bizi arayın.", "sms_lang": 1 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'title' => 'BASLIGINIZ', 'sentto' => '905XXXXXXXXX,905YYYYYYYYY', 'text' => 'Randevunuz yarın 10:00\'da.TS-LBilgi için bizi arayın.', 'sms_lang' => 1, ]; $ch = curl_init('https://api.turkeysms.com.tr/sms/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": "905XXXXXXXXX,905YYYYYYYYY", "text": "Randevunuz yarın 10:00'da.TS-LBilgi için bizi arayın.", "sms_lang": 1, } r = requests.post("https://api.turkeysms.com.tr/sms/send", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', title: 'BASLIGINIZ', sentto: '905XXXXXXXXX,905YYYYYYYYY', text: 'Randevunuz yarın 10:00\'da.TS-LBilgi için bizi arayın.', sms_lang: 1, }; const r = await fetch('https://api.turkeysms.com.tr/sms/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1024", "result_message": "SMS dispatched successfully.", "sms_id": 48213377, "number_of_sms": 2, "total_recipients": 2, "success_count": 2, "sms_lang": "Turkish", "content_type": "Transactional", "country": "Turkey-TR" }
| Field | Description |
|---|---|
sms_id | The message ID of the last recipient. For single-recipient sends, use it with SMS status query. |
number_of_sms | The total SMS count for all recipients. |
total_recipients | The number of recipients after duplicates are removed. |
success_count | The number of recipients accepted for sending. It is not the number of delivered messages; for delivery status, use Webhook or SMS status query. |
sms_lang, content_type | Labels for the values you sent. |
country | The country label of the first recipient (for example, Turkey-TR, or GlobalSMS-GL for international numbers). |
{ "result": false, "result_code": "TS-1027", "result_message": "Insufficient SMS credits. Please top up your account." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is not valid JSON or form data. |
| TS-1050 | 401 | api_key is missing or shorter than 30 characters. |
| TS-1025 | 400 | sentto is missing, shorter than 7 characters or contains no number. |
| TS-1051 | 400 | title is missing. |
| TS-1029 | 400 | title is longer than 11 characters. |
| TS-1026 | 400 | text is empty or longer than 2,000 characters. |
| TS-1060 | 400 | The recipient limit was exceeded: 500 (50,000 for scheduled sends). |
| TS-1070 / TS-1071 / TS-1072 | 400 | The scheduling fields are invalid (see Scheduled sending). |
| TS-1031 | 401 | The key was not found or is not active. |
| TS-1061 | 403 | The “Allow POST requests” permission is off. |
| TS-1062 | 403 | The “Send SMS” permission is off. |
| TS-1030 | 403 | The account is not active. |
| TS-1028 | 400 | The Sender ID was not found on your account or is not approved. |
| TS-1027 | 403 | Insufficient balance. |
| TS-1060 | 429 | The per-minute send limit was exceeded. |
| SRV-ERR | 500 | Unexpected server error. |
sms.sent, sms.delivered and sms.failed events are sent for this send. For which webhook an event goes to, see Webhook → Routing.When scheduled_date and scheduled_time are added to a /sms/send request, the messages are not sent immediately; they are queued to be sent at the specified time.
Required permissions: “Allow POST requests” and “Send SMS” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| scheduled_date | string | Required | Send date, YYYY-MM-DD (for example, 2026-10-15). |
| scheduled_time | string | Required | Send time, HH:MM or HH:MM:ss (for example, 09:30). |
The other parameters are the same as in Sending SMS.
curl -X POST https://api.turkeysms.com.tr/sms/send \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": "905XXXXXXXXX", "text": "Kampanyamız yarın başlıyor.", "sms_lang": 1, "scheduled_date": "2026-10-15", "scheduled_time": "09:30" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'title' => 'BASLIGINIZ', 'sentto' => '905XXXXXXXXX', 'text' => 'Kampanyamız yarın başlıyor.', 'sms_lang' => 1, 'scheduled_date' => '2026-10-15', 'scheduled_time' => '09:30', ]; $ch = curl_init('https://api.turkeysms.com.tr/sms/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": "905XXXXXXXXX", "text": "Kampanyamız yarın başlıyor.", "sms_lang": 1, "scheduled_date": "2026-10-15", "scheduled_time": "09:30", } r = requests.post("https://api.turkeysms.com.tr/sms/send", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', title: 'BASLIGINIZ', sentto: '905XXXXXXXXX', text: 'Kampanyamız yarın başlıyor.', sms_lang: 1, scheduled_date: '2026-10-15', scheduled_time: '09:30', }; const r = await fetch('https://api.turkeysms.com.tr/sms/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1024", "result_message": "SMS dispatched successfully.", "sms_id": 734129955, "number_of_sms": 1, "total_recipients": 1, "success_count": 1, "sms_lang": "Turkish", "content_type": "Transactional", "country": "Turkey-TR" }
For a scheduled send, sms_id is the send's report ID. Use this value as raporid in the Reports endpoints; SMS status query does not recognize this ID.
{ "result": false, "result_code": "TS-1072", "result_message": "Scheduled time must not be in the past." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1070 | 400 | scheduled_date is not in YYYY-MM-DD format or scheduled_time is missing. |
| TS-1071 | 400 | scheduled_time is not in HH:MM or HH:MM:ss format. |
| TS-1072 | 400 | The specified time is in the past or invalid (for example, 25:99). |
| TS-1060 | 400 | The 50,000 number limit was exceeded. |
The other codes are the same as in Sending SMS.
Sends to many numbers with a single request. There are two endpoints:
/group/send: the same text to all numbers./group/sendMixed: each number gets its own text. text is an array whose order matches the number array.Required permission: “Send to group” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| title | string | Required | A Sender ID approved on your account. |
| sentto | array | Required | An array of numbers (a JSON array; comma-separated text is not accepted). At most 50,000 items. It can also be sent as numbers. |
| text | string / array | Required | /group/send: a single text. /group/sendMixed: an array of texts with the same length as the number array; text[i] goes to the number sentto[i]. Each text is at most 2,000 characters; TS-L is converted to a line break. |
| sms_lang | int | Optional | 0 English, 1 Turkish, 2 Arabic/Unicode. Default 2. For Turkish text, send 1. |
| scheduled_sms | int | Optional | If 1 is sent, the send is scheduled. |
| scheduled_date | string | Conditional | Required if scheduled_sms is 1. YYYY-MM-DD. |
| scheduled_time | string | Conditional | Required if scheduled_sms is 1. HH:MM or HH:MM:ss. The date and time are evaluated in Türkiye time (Europe/Istanbul). |
/sms/send: spaces, +, - and parentheses are removed; the 00 is removed from numbers that start with 00; and 05… and 10-digit 5… numbers are converted to the 905… format. Numbers are not validated one by one./group/send request, a repeated number is queued once. In a /group/sendMixed request, an identical number and text pair is queued once; different texts to the same number are sent separately.TS-1027).scheduled_sms: 1.curl -X POST https://api.turkeysms.com.tr/group/send \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": [ "905XXXXXXXXX", "905YYYYYYYYY" ], "text": "Mağazamız bugün 21:00'\''e kadar açıktır.", "sms_lang": 1 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'title' => 'BASLIGINIZ', 'sentto' => ['905XXXXXXXXX', '905YYYYYYYYY'], 'text' => 'Mağazamız bugün 21:00\'e kadar açıktır.', 'sms_lang' => 1, ]; $ch = curl_init('https://api.turkeysms.com.tr/group/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": ["905XXXXXXXXX", "905YYYYYYYYY"], "text": "Mağazamız bugün 21:00'e kadar açıktır.", "sms_lang": 1, } r = requests.post("https://api.turkeysms.com.tr/group/send", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', title: 'BASLIGINIZ', sentto: ['905XXXXXXXXX', '905YYYYYYYYY'], text: 'Mağazamız bugün 21:00\'e kadar açıktır.', sms_lang: 1, }; const r = await fetch('https://api.turkeysms.com.tr/group/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
curl -X POST https://api.turkeysms.com.tr/group/sendMixed \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": [ "905XXXXXXXXX", "905YYYYYYYYY" ], "text": [ "Sayın Ayşe Hanım, siparişiniz hazır.", "Sayın Mehmet Bey, siparişiniz hazır." ], "sms_lang": 1 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'title' => 'BASLIGINIZ', 'sentto' => ['905XXXXXXXXX', '905YYYYYYYYY'], 'text' => ['Sayın Ayşe Hanım, siparişiniz hazır.', 'Sayın Mehmet Bey, siparişiniz hazır.'], 'sms_lang' => 1, ]; $ch = curl_init('https://api.turkeysms.com.tr/group/sendMixed'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "title": "BASLIGINIZ", "sentto": ["905XXXXXXXXX", "905YYYYYYYYY"], "text": ["Sayın Ayşe Hanım, siparişiniz hazır.", "Sayın Mehmet Bey, siparişiniz hazır."], "sms_lang": 1, } r = requests.post("https://api.turkeysms.com.tr/group/sendMixed", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', title: 'BASLIGINIZ', sentto: ['905XXXXXXXXX', '905YYYYYYYYY'], text: ['Sayın Ayşe Hanım, siparişiniz hazır.', 'Sayın Mehmet Bey, siparişiniz hazır.'], sms_lang: 1, }; const r = await fetch('https://api.turkeysms.com.tr/group/sendMixed', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1024", "result_message": "Bulk SMS dispatched successfully.", "rapor_id": 482913377, "total_numbers": 2, "total_sms_cost": 2, "scheduled": false }
| Field | Description |
|---|---|
rapor_id | The send's report ID. Used as raporid in the Reports endpoints. |
total_numbers | The number of numbers queued after conversion and de-duplication. |
total_sms_cost | The total SMS count. |
scheduled | true if the send was scheduled. |
{ "result": false, "result_code": "TS-1028", "result_message": "Sender ID was not found in your account or is not approved." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is invalid, text is empty, text has the wrong type, or in /group/sendMixed the number of texts and numbers is not equal. |
| TS-1025 | 400 | api_key is missing or shorter than 30 characters. |
| TS-1029 | 400 | title is missing. |
| TS-1026 | 400 | sentto is missing or not an array, or a text is longer than 2,000 characters. |
| TS-1060 | 400 | The 50,000 number limit was exceeded. |
| TS-1070 / TS-1071 / TS-1072 | 400 | The scheduling fields are invalid: date format, time format or a time in the past. |
| TS-1031 | 403 | The key was not found or is not active. |
| TS-1067 | 403 | The “Send to group” permission is off. |
| TS-1030 | 403 | The account is not active. |
| TS-1028 | 403 | The Sender ID was not found on your account or is not approved. |
| TS-1027 | 403 | Insufficient balance. |
| SRV-ERR | 500 | Unexpected server error. |
Sends a verification code (OTP) generated by TurkeySMS to a single number. The text comes from a fixed template; the sender name is always OTPSMS.
Required permission: “Send OTP” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| mobile | string | Required | A single recipient number. Send it in the 905XXXXXXXXX format; the 05…, 5… and 00… formats are also converted. |
| digits | int | Optional | Code length: 4, 5 or 6. Default 4; for any other value, 4 is used. |
| sms_lang | int | Optional | Template language: 0 English, 1 Turkish, 2 Arabic. Default 2. It can also be sent as lang; if both are sent, sms_lang is used. |
MARKA is the OTP brand name set on your account. In the example, the code is 4821.
4821 Aktivasyon kodunuz OTP MARKA
Your activation code is:4821 MARKA
4821 هو رمز التفعيل الخاص بك MARKA
curl -X POST https://api.turkeysms.com.tr/otp/send \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "mobile": "905XXXXXXXXX", "digits": 6, "sms_lang": 1 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'mobile' => '905XXXXXXXXX', 'digits' => 6, 'sms_lang' => 1, ]; $ch = curl_init('https://api.turkeysms.com.tr/otp/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "mobile": "905XXXXXXXXX", "digits": 6, "sms_lang": 1, } r = requests.post("https://api.turkeysms.com.tr/otp/send", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', mobile: '905XXXXXXXXX', digits: 6, sms_lang: 1, }; const r = await fetch('https://api.turkeysms.com.tr/otp/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1024", "result_message": "OTP dispatched successfully.", "sms_id": 48213390, "otp_code": 482193, "sandbox": false }
| Field | Description |
|---|---|
sms_id | The message ID; can be used with SMS status query. |
otp_code | The code that was sent. It is returned as an integer and does not start with 0. |
sandbox | false in live requests. |
otp_code value on your server with a short validity period (for example, 3–5 minutes), compare it with the code the user enters, and delete it after use. Do not send the code to the client (browser, mobile app). On your side, limit how many requests can be made to the same number within a short time.{ "result": false, "result_code": "TS-1036", "result_message": "OTP sending privilege is disabled for this API key." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is invalid. |
| TS-1050 | 401 | api_key is missing or shorter than 30 characters. |
| TS-1025 | 400 | mobile is missing or shorter than 7 characters. |
| TS-1034 | 403 | The number format is invalid (it must be 11–15 digits after cleanup). |
| TS-1031 | 403 | The key was not found or is not active. |
| TS-1036 | 403 | The “Send OTP” permission is off. |
| TS-1030 | 403 | The account is not active. |
| TS-1027 | 403 | Insufficient balance. |
| TS-5000 | 403 | The message could not be saved; retry the request. |
| SRV-ERR | 500 | Unexpected server error. |
Sends an OTP with your own Sender ID and your own text. TurkeySMS generates the code and puts it in place of the TS-CODE token in the text.
Required permissions: “Allow POST requests” and “Advanced OTP” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| mobile | string | Required | A single recipient number, 905XXXXXXXXX. |
| title | string | Required | A Sender ID on your account. At most 11 characters; Turkish characters count as two characters. The Sender ID must be approved, its document must be approved and the operator approval must be complete. |
| text | string | Required | Message text; it must contain the TS-CODE token (in capitals). At most 2,000 characters. TS-L is converted to a line break. |
| lang | int | Optional | 0 English, 1 Turkish, 2 Arabic/Unicode. Default 2. This endpoint does not read sms_lang; use lang. |
| digits | int | Optional | Code length: 4, 5 or 6. Default 4. |
test, api, apikey, api_key, test123, 123, 0000, 123456789, senderid, sender, title, text, content.curl -X POST https://api.turkeysms.com.tr/otp/detailed \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "mobile": "905XXXXXXXXX", "title": "BASLIGINIZ", "text": "Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.", "lang": 1, "digits": 6 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'mobile' => '905XXXXXXXXX', 'title' => 'BASLIGINIZ', 'text' => 'Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.', 'lang' => 1, 'digits' => 6, ]; $ch = curl_init('https://api.turkeysms.com.tr/otp/detailed'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "mobile": "905XXXXXXXXX", "title": "BASLIGINIZ", "text": "Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.", "lang": 1, "digits": 6, } r = requests.post("https://api.turkeysms.com.tr/otp/detailed", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', mobile: '905XXXXXXXXX', title: 'BASLIGINIZ', text: 'Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.', lang: 1, digits: 6, }; const r = await fetch('https://api.turkeysms.com.tr/otp/detailed', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1024", "result_message": "OTP dispatched successfully.", "sms_id": 48213391, "otp_code": 482193, "number_of_sms": 1, "sms_lang": "Turkish", "sandbox": false }
| Field | Description |
|---|---|
sms_id | The message ID. |
otp_code | The code that was sent. It is returned as an integer and does not start with 0. |
number_of_sms | The SMS count of the message. |
sms_lang | The label of the lang value. |
sandbox | false in live requests. |
TS-1024. You can check the message status with SMS status query; on a delivery error it returns TS-1022 with the error description in the details field. The warning about verifying the code in Sending OTP also applies here.{ "result": false, "result_code": "TS-1028", "result_message": "Sender ID is not approved for OTP (approval, document and network approval are required)." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is invalid, a field is not a string, or the Sender ID is on the not-accepted list. |
| TS-1050 | 400 | api_key is missing. |
| TS-1025 | 400 | mobile is missing. |
| TS-1051 | 400 | title is missing. |
| TS-1026 | 400 | text is empty, does not contain TS-CODE or is longer than 2,000 characters. |
| TS-1031 | 400 403 | 400: the key is shorter than 30 characters. 403: the key was not found or is not active. |
| TS-1034 | 400 | The number format is invalid. |
| TS-1029 | 400 403 | 400: the Sender ID is longer than 11 characters. 403: the Sender ID was not found on your account. |
| TS-1061 | 403 | The “Allow POST requests” permission is off. |
| TS-1037 | 403 | The “Advanced OTP” permission is off. |
| TS-1030 | 403 | The account is not active. |
| TS-1028 | 403 | The Sender ID is not approved for OTP (approval, document or operator approval is missing). |
| TS-1027 | 403 | Insufficient balance. |
| TS-5000 | 403 | The message could not be saved; retry the request. |
| SRV-ERR | 500 | Unexpected server error. |
sms_lang (lang in Advanced OTP) sets the message's character set and how many SMS it counts as. A wrong value can cause the text to be split into more SMS.
| Value | Use |
|---|---|
0 | English; Latin letters and standard symbols only (no Turkish characters). |
1 | Turkish; text that contains ç, ğ, ı, İ, ö, ş, ü. |
2 | Arabic and other Unicode text. This is the default value. |
The numbers in the table are the maximum number of characters that fit in the given SMS count. The character count is calculated after TS-L is converted to a line break.
| Text length up to | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
Türkiye numbers, sms_lang 0 | 160 | 305 | 455 | 610 | 760 | 910 | 1070 |
Türkiye numbers, sms_lang 1 | 155 | 245 | 445 | 595 | 740 | 890 | 1040 |
Türkiye numbers, sms_lang 2 | 65 | 127 | 190 | 250 | 315 | 380 | 445 |
| International numbers (all values) | 70 | 130 | 195 | 260 | 325 | 390 | 450 |
/sms/send and /otp/detailed treat numbers that do not start with 905 as international numbers. /group/send and /group/sendMixed use the Türkiye table for all numbers.content_type in a /sms/send request (0 Transactional, 1 High Quality, 2 Advertising) is returned only as a label in the response; it does not change the sending route or the price.
Creates, renames, deletes and lists the groups in your contacts. Each operation has its own permission.
TS-1082, TS-1086). Always evaluate the result with result and result_code.Required permission: “Create group” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| group_name | string | Required | Group name. 2–50 characters; Turkish characters count as two characters. It must be unique among your active groups. |
curl -X POST https://api.turkeysms.com.tr/groups/create \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "group_name": "Müşteriler" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'group_name' => 'Müşteriler', ]; $ch = curl_init('https://api.turkeysms.com.tr/groups/create'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "group_name": "Müşteriler", } r = requests.post("https://api.turkeysms.com.tr/groups/create", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', group_name: 'Müşteriler', }; const r = await fetch('https://api.turkeysms.com.tr/groups/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1080", "result_message": "Group created successfully.", "group": { "id": 5412, "name": "Müşteriler", "created_at": "2026-10-01" } }
Use the group.id value as group_id when adding numbers.
Required permission: “Edit group” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| group_id | int | Required | Group ID. |
| new_name | string | Required | New name. This endpoint does not check length or uniqueness; we recommend keeping the name 2–50 characters long and unique. |
curl -X POST https://api.turkeysms.com.tr/groups/edit \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "group_id": 5412, "new_name": "VIP Müşteriler" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'group_id' => 5412, 'new_name' => 'VIP Müşteriler', ]; $ch = curl_init('https://api.turkeysms.com.tr/groups/edit'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "group_id": 5412, "new_name": "VIP Müşteriler", } r = requests.post("https://api.turkeysms.com.tr/groups/edit", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', group_id: 5412, new_name: 'VIP Müşteriler', }; const r = await fetch('https://api.turkeysms.com.tr/groups/edit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1087", "result_message": "Group name updated successfully.", "new_name": "VIP Müşteriler" }
Required permission: “Delete group” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| group_id | int | Required | Group ID. |
A deleted group is removed from the lists and cannot be used again. Numbers added to the group are not deleted.
curl -X POST https://api.turkeysms.com.tr/groups/delete \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "group_id": 5412 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'group_id' => 5412, ]; $ch = curl_init('https://api.turkeysms.com.tr/groups/delete'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "group_id": 5412, } r = requests.post("https://api.turkeysms.com.tr/groups/delete", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', group_id: 5412, }; const r = await fetch('https://api.turkeysms.com.tr/groups/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1088", "result_message": "Group deleted successfully." }
Required permission: “List groups” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| search | string | Optional | Search within the name. If empty, all groups are returned. |
curl -X POST https://api.turkeysms.com.tr/groups/list \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "search": "Müşteri" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'search' => 'Müşteri', ]; $ch = curl_init('https://api.turkeysms.com.tr/groups/list'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "search": "Müşteri", } r = requests.post("https://api.turkeysms.com.tr/groups/list", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', search: 'Müşteri', }; const r = await fetch('https://api.turkeysms.com.tr/groups/list', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1090", "result_message": "Group list retrieved successfully.", "groups_count": 2, "groups": [ { "id": 5413, "name": "VIP Müşteriler", "date": "2026-10-01" }, { "id": 5398, "name": "Müşteriler 2025", "date": "2026-04-02" } ] }
Only groups that have not been deleted are returned, newest first. There is no pagination.
{ "result": false, "result_code": "TS-1082", "result_message": "Group name already exists." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1080 / TS-1087 / TS-1088 / TS-1090 | 200 | Created / updated / deleted / listed. |
| TS-1033 | 400 | The body is invalid. |
| TS-1050 | 400 | api_key is missing. |
| TS-1031 | 400 | The key is shorter than 30 characters, was not found or is not active. |
| TS-1081 / TS-1084 / TS-1085 / TS-1089 | 400 | The related permission is off: create / edit / delete / list. |
| TS-1030 | 400 | The account is not active. |
| TS-1082 | 200 | An active group with this name already exists. |
| TS-1083 | 400 200 | The group name is empty or outside 2–50 characters. In edit, it is also returned with 200 for an invalid group_id. |
| TS-1086 | 400 200 | 400: group_id is missing. 200: the group was not found, does not belong to you or has been deleted. |
| SRV-ERR | 500 | Unexpected server error. |
Adds a number to a group. The number is saved to your contacts together with a name and three extra fields.
Required permission: “Add number” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| group_id | int | Required | The ID of the group the number is added to (see Groups). |
| gsm_number | string | Required | A Türkiye mobile number. 905XXXXXXXXX, 05XXXXXXXXX, 5XXXXXXXXX, +905… and 00905… are accepted; it is saved as 905XXXXXXXXX. |
| name | string | Optional | The contact's name. |
| f_01, f_02, f_03 | string | Optional | Extra fields; free text for personalization. |
curl -X POST https://api.turkeysms.com.tr/contacts/add \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "group_id": 5412, "gsm_number": "905XXXXXXXXX", "name": "Ayşe Yılmaz", "f_01": "İstanbul" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'group_id' => 5412, 'gsm_number' => '905XXXXXXXXX', 'name' => 'Ayşe Yılmaz', 'f_01' => 'İstanbul', ]; $ch = curl_init('https://api.turkeysms.com.tr/contacts/add'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "group_id": 5412, "gsm_number": "905XXXXXXXXX", "name": "Ayşe Yılmaz", "f_01": "İstanbul", } r = requests.post("https://api.turkeysms.com.tr/contacts/add", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', group_id: 5412, gsm_number: '905XXXXXXXXX', name: 'Ayşe Yılmaz', f_01: 'İstanbul', }; const r = await fetch('https://api.turkeysms.com.tr/contacts/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1100", "result_message": "Contact added successfully.", "group_id": 5412, "total_sent": 1, "total_added": 1, "total_failed": 0, "mobile": "905XXXXXXXXX" }
mobile is the number that was saved. total_sent, total_added and total_failed summarize this single-number operation.
{ "result": false, "result_code": "TS-1101", "result_message": "Failed to add contact." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is invalid. |
| TS-1050 | 400 | api_key is missing. |
| TS-1025 | 400 | gsm_number is missing. |
| TS-1086 | 400 200 | 400: group_id is missing, or the group was not found or does not belong to you. 200: group_id is not a number or is not greater than zero. |
| TS-1031 | 400 | The key was not found or is not active. |
| TS-1065 | 400 | The “Add number” permission is off. |
| TS-1030 | 400 | The account is not active. |
| TS-1101 | 200 | The number is not a valid Türkiye mobile number. |
| SRV-ERR | 500 | Unexpected server error. |
Adds numbers to your number blocking list and checks whether a number is on the list. The list is per account.
/sms/send, /otp/* and /group/*; if you send through the API, filter out blocked numbers on your side.Required permission: “Block number” (API Center → My Keys → key → Scopes)
The permission is required for both endpoints. The old /blacklist/add and /blacklist/status URLs are not used (404).
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| number | string | Required | Türkiye mobile number. The 905XXXXXXXXX, +90 5XX…, 0090 5XX…, 05XX… and 5XX… formats are accepted and converted to the 905XXXXXXXXX format. |
905XXXXXXXXX format, with the same rule as the panel. Adding and querying compare the last 10 digits of the number, so 05321234567 and 905321234567 count as the same number. Landline and foreign numbers are not accepted (TS-1144).Response format difference: these endpoints use the status field ("success" or "error") instead of result.
curl -X POST https://api.turkeysms.com.tr/blacklist/post/add \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "number": "905XXXXXXXXX" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'number' => '905XXXXXXXXX', ]; $ch = curl_init('https://api.turkeysms.com.tr/blacklist/post/add'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['status'] ?? '') === 'success') { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "number": "905XXXXXXXXX", } r = requests.post("https://api.turkeysms.com.tr/blacklist/post/add", json=payload, timeout=30) data = r.json() if data.get("status") == "success": print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', number: '905XXXXXXXXX', }; const r = await fetch('https://api.turkeysms.com.tr/blacklist/post/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.status === 'success') { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "status": "success", "result_code": "TS-1141", "result_message": "Number added to blacklist successfully" }
curl -X POST https://api.turkeysms.com.tr/blacklist/post/status \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "number": "905XXXXXXXXX" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'number' => '905XXXXXXXXX', ]; $ch = curl_init('https://api.turkeysms.com.tr/blacklist/post/status'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['status'] ?? '') === 'success') { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "number": "905XXXXXXXXX", } r = requests.post("https://api.turkeysms.com.tr/blacklist/post/status", json=payload, timeout=30) data = r.json() if data.get("status") == "success": print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', number: '905XXXXXXXXX', }; const r = await fetch('https://api.turkeysms.com.tr/blacklist/post/status', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.status === 'success') { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "status": "success", "result_code": "TS-1143", "result_message": "The phone number is currently in the blacklist.", "is_blocked": true, "block_date": "2026-10-01", "block_time": "14:30" }
{ "status": "success", "result_code": "TS-1142", "result_message": "The phone number is NOT in the blacklist.", "is_blocked": false }
block_date and block_time are the date and time the number was added to the list. To view the list and remove numbers, use the Number Blocking page in the panel; the API has no remove endpoint.
{ "status": "error", "result_code": "TS-1065", "result_message": "Number blocking privilege is disabled for this API key." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1141 | 200 | The number was added to the list. |
| TS-1143 / TS-1142 | 200 | The number is on the list / not on the list. |
| TS-1050 | 403 | api_key is missing or the body is invalid. |
| TS-1031 | 401 | The key is shorter than 20 characters, was not found or is not active, or the account is not active. |
| TS-1025 | 400 | number is missing. |
| TS-1144 | 400 | The number is not a valid Türkiye mobile number. |
| TS-1065 | 403 | The “Block number” permission is off. |
| TS-1140 | 400 | The number is already on your list. |
| TS-1033 | 400 | An error occurred while saving; retry. |
| TS-404 | 404 | Wrong path. |
Returns the SMS credit in your account.
Required permission: “Check balance” (API Center → My Keys → key → Scopes)
/balance/. The URL without the slash returns a redirect (301); some HTTP clients turn the POST request into GET on a redirect, and the request fails.| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
curl -X POST https://api.turkeysms.com.tr/balance/ \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', ]; $ch = curl_init('https://api.turkeysms.com.tr/balance/'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", } r = requests.post("https://api.turkeysms.com.tr/balance/", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', }; const r = await fetch('https://api.turkeysms.com.tr/balance/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1040", "result_message": "Balance retrieved successfully.", "balance_main": 1500 }
balance_main: SMS credit (integer).
{ "result": false, "result_code": "TS-1065", "result_message": "Balance inquiry privilege is disabled for this key." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is invalid. |
| TS-1050 | 400 | api_key is missing. |
| TS-1025 | 400 | api_key is shorter than 30 characters. |
| TS-1031 | 403 | The key was not found or is not active. |
| TS-1065 | 403 | The “Check balance” permission is off. |
| TS-1030 | 403 | The account is not active. |
| SRV-ERR | 403 500 | Unexpected server error. |
Lists the Sender IDs approved on your account. In sends, put a Sender ID from this list in the title field.
Required permission: “Check Sender ID” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
curl -X POST https://api.turkeysms.com.tr/senderid/check \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ" }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', ]; $ch = curl_init('https://api.turkeysms.com.tr/senderid/check'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", } r = requests.post("https://api.turkeysms.com.tr/senderid/check", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', }; const r = await fetch('https://api.turkeysms.com.tr/senderid/check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1040", "result_message": "Operation success.", "sender_ids_count": 2, "sender_ids": [ { "id": 1288, "title": "BASLIGINIZ", "status": 1, "network_stat": 1 }, { "id": 1102, "title": "MAGAZA", "status": 1, "network_stat": 1 } ] }
| Field | Description |
|---|---|
sender_ids_count | The number of Sender IDs in the list. |
sender_ids[].id | Sender ID record ID. |
sender_ids[].title | The Sender ID; used as title in sends. |
sender_ids[].status | Always 1, because only approved Sender IDs are listed. |
sender_ids[].network_stat | Its values are not defined yet; do not use it in your integration. |
The list starts with the newest Sender ID. Sender IDs that are pending approval or rejected are not listed; if there is no approved Sender ID, sender_ids is an empty array.
{ "result": false, "result_code": "TS-1038", "result_message": "Sender ID inquiry privilege is disabled for this key." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is invalid. |
| TS-1050 | 400 | api_key is missing. |
| TS-1031 | 400 403 | 400: the key is shorter than 30 characters. 403: the key was not found or is not active. |
| TS-1038 | 403 | The “Check Sender ID” permission is off. |
| TS-1030 | 403 | The account is not active. |
| SRV-ERR | 403 500 | Unexpected server error. |
Returns the delivery status of a single message. Only messages sent from your own account can be queried.
Required permissions: “Allow POST requests” and “Check SMS status” (API Center → My Keys → key → Scopes)
| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| sms_id | int | Required | Message ID: the sms_id in the response of an immediate /sms/send (single recipient), /otp/send or /otp/detailed. |
curl -X POST https://api.turkeysms.com.tr/sms/status \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "sms_id": 48213377 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'sms_id' => 48213377, ]; $ch = curl_init('https://api.turkeysms.com.tr/sms/status'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['result'] ?? false) === true) { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "sms_id": 48213377, } r = requests.post("https://api.turkeysms.com.tr/sms/status", json=payload, timeout=30) data = r.json() if data.get("result") is True: print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', sms_id: 48213377, }; const r = await fetch('https://api.turkeysms.com.tr/sms/status', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.result === true) { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "result": true, "result_code": "TS-1064", "result_message": "Number received the message.", "sender_id": "BASLIGINIZ", "date_of_sending": "2026-10-01", "time_of_sending": "14:27:12", "sms_status": "Number received the message", "sms_balance": "1 SMS", "details": "OK", "operator": "Turkcell" }
| Field | Description |
|---|---|
result_code | TS-1064: the message was delivered. TS-1022: no delivery confirmation (the message could not be delivered or the delivery report has not arrived yet). Both are returned with result: true and HTTP 200. |
sms_status | Number received the message or The number did not receive the message. |
sender_id | The Sender ID the message was sent with. |
date_of_sending, time_of_sending | The date and time the message was recorded. |
sms_balance | The SMS count of this message (not the account balance). |
details | The operation result; on a delivery error, the error description. |
operator | The recipient's operator; may be empty if unknown. |
{ "result": false, "result_code": "TS-1020", "result_message": "The data sent is incorrect." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1033 | 400 | The body is invalid. |
| TS-1050 | 403 | api_key is missing. |
| TS-1052 | 403 | sms_id is missing, is not a number or is not greater than zero. |
| TS-1031 | 403 | The key was not found or is not active. |
| TS-1061 | 403 | The “Allow POST requests” permission is off. |
| TS-1063 | 403 | The “Check SMS status” permission is off. |
| TS-1030 | 403 | The account is not active. |
| TS-1020 | 403 | No message of yours was found with this ID. |
| SRV-ERR | 500 | Unexpected server error. |
Returns reports for group and scheduled sends. There are two endpoints: the Summary report gives the send's counters, and the Detailed report gives the delivery status per number.
Required permissions: “Allow POST requests” and “Check SMS status” (API Center → My Keys → key → Scopes)
raporid value is the rapor_id in the /group/send and /group/sendMixed response, or the sms_id in a scheduled /sms/send response. These endpoints use the status field instead of result; a successful response has no result_message.| Parameter | Type | Status | Description |
|---|---|---|---|
| api_key | string | Required | Your API key. |
| raporid | int | Required | Report ID. |
| page | int | Optional | Detailed report only: page number, 1 or greater. Default 1. Each page returns at most 500 records. |
curl -X POST https://api.turkeysms.com.tr/reports/basic \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "raporid": 482913377 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'raporid' => 482913377, ]; $ch = curl_init('https://api.turkeysms.com.tr/reports/basic'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['status'] ?? '') === 'success') { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "raporid": 482913377, } r = requests.post("https://api.turkeysms.com.tr/reports/basic", json=payload, timeout=30) data = r.json() if data.get("status") == "success": print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', raporid: 482913377, }; const r = await fetch('https://api.turkeysms.com.tr/reports/basic', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.status === 'success') { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "status": "success", "result_code": "TS-1064", "rapor_id": 482913377, "total_numbers": 100, "success_count": 90, "failed_count": 5, "pending_count": 5, "details": { "sending_date": "2026-10-01", "sending_time": "14:22:10", "report_status": "Currently in the process of being sent.", "sms_sender_id": "BASLIGINIZ", "invalid_numbers": 2, "blocked_numbers": 1, "last_update": "2026-10-01 14:30:00" } }
| Field | Description |
|---|---|
total_numbers | The number of numbers in the send. |
success_count | The number of successful messages. |
failed_count | Failed messages, including invalid and blocked numbers. |
pending_count | Messages whose result is not known yet. |
details.sending_date, details.sending_time | The date and time the report was created (for a scheduled send, this is not the send time). |
details.report_status | Text that describes the report's processing state. |
details.sms_sender_id | The Sender ID used in the send. |
details.invalid_numbers, details.blocked_numbers | The number of invalid numbers and the number of blocked numbers. |
details.last_update | The time the counters were last updated. |
The counters change as the sending and delivery report processes progress.
curl -X POST https://api.turkeysms.com.tr/reports/detailed \ -H "Content-Type: application/json" \ -d '{ "api_key": "API_ANAHTARINIZ", "raporid": 482913377, "page": 1 }'
$payload = [ 'api_key' => 'API_ANAHTARINIZ', 'raporid' => 482913377, 'page' => 1, ]; $ch = curl_init('https://api.turkeysms.com.tr/reports/detailed'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 30, ]); $body = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $res = $body !== false ? json_decode($body, true) : null; if (($res['status'] ?? '') === 'success') { // Request succeeded } else { error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response')); }
import requests payload = { "api_key": "API_ANAHTARINIZ", "raporid": 482913377, "page": 1, } r = requests.post("https://api.turkeysms.com.tr/reports/detailed", json=payload, timeout=30) data = r.json() if data.get("status") == "success": print("Success", data) else: print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch) (async () => { const payload = { api_key: 'API_ANAHTARINIZ', raporid: 482913377, page: 1, }; const r = await fetch('https://api.turkeysms.com.tr/reports/detailed', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await r.json(); if (data.status === 'success') { console.log('Success', data); } else { console.error('Error', r.status, data.result_code); } })();
{ "status": "success", "result_code": "TS-1064", "data": [ { "phone_number": "905XXXXXXXXX", "sent_at": "2026-10-01 14:22:10", "sms_status": "Number received the message", "details": { "done_at": "2026-10-01 14:22:15", "status_code": 1, "operator": "TURKCELL" } } ], "pagination": { "current_page": 1, "total_pages": 3, "total_records": 1203, "records_per_page": 500 } }
| details.status_code | sms_status | Meaning |
|---|---|---|
1 | Number received the message | Delivered. |
2 | Expiration time | The validity period expired; not delivered. |
0 | Number didn't receive the message | Not delivered, or the delivery report has not arrived yet. |
details.operator values: TURKCELL, VODAFONE, TURKTELEKOM, KKTCELL, TELSIM, UNKNOWN. If there are no records yet (for example, if a scheduled send has not started), data is an empty array. An empty array is also returned for pages after the last page.
{ "status": "error", "result_code": "TS-1029", "result_message": "The Report ID is invalid or missing." }
| Code | HTTP | Meaning |
|---|---|---|
| TS-1064 | 200 | The report was returned. |
| TS-1029 | 400 200 | 400: raporid is missing or not a number. 200: the report was not found or does not belong to you. |
| TS-1033 | 400 404 | 400: the body or page is invalid. 404: wrong path. |
| TS-1050 | 400 | api_key is missing. |
| TS-1031 | 400 | The key is shorter than 30 characters, was not found or is not active. |
| TS-1061 | 400 | The “Allow POST requests” permission is off. |
| TS-1063 | 400 | The “Check SMS status” permission is off. |
| TS-1030 | 400 | The account is not active. |
| SRV-ERR | 500 | Unexpected server error. |
With webhooks, TurkeySMS notifies an address you choose about events on your account (a message being passed to the operator, a delivery report, an inbound SMS and so on) with an HTTP POST request. This way you do not need to keep querying the API (polling) to learn a message's status.
The request and body examples in this section are identical in format to what the live system sends; the values in them (numbers, IDs, times) are examples.
| Old | New |
|---|---|
| X-TurkeySMS-Webhook-Id / event_id | X-TurkeySMS-Delivery / id |
| X-TurkeySMS-Webhook-Version / version | Removed |
| X-TurkeySms-Signature (body only) | X-TurkeySMS-Signature-V2 (timestamped); the old header is still sent for compatibility |
| timestamp (ISO text) | timestamp (Unix seconds, number) and created_at (ISO 8601) |
| generated_at | Removed |
| data.sms_id | data.message_id |
| data.mobile | data.to |
| data.delivered_at | data.done_at |
| data.failure_reason | data.reason |
| data.operator | Removed (operator_status is the operator's status text) |
id.POST.2xx within 10 seconds, the event counts as delivered. On temporary errors (connection error, timeout, 408, 429, 5xx), the event is retried as many times as you configured.Typical notification times for events:
| Event | When it is sent | Typical delay |
|---|---|---|
| sms.sent | When the message is passed to the operator | About 1.5–2 minutes (the system waits 90 seconds for the message record to be completed) |
| sms.delivered | When a successful delivery report arrives from the operator | Within 30–60 seconds after the report arrives (if the report arrives very quickly, together with sms.sent, 1.5–2 minutes after sending) |
| sms.failed | When the message could not be passed to the operator or could not be delivered to the recipient | For a delivery failure, 30–60 seconds after the report arrives; for a message that could not be passed to the operator, about 6–7 minutes after it was recorded |
| sms.received | When a message reaches your inbound SMS number (0850) | Usually within 30 seconds |
| inbound.matched | When the “Fire Webhook” action in an automation rule runs | Usually within 30 seconds |
| key.test | When you press the Test button in the panel | The moment you press the button (synchronous) |
sms.delivered or sms.failed is not sent for that message. You can always query a message's final status with SMS status query. The otp.verified event appears as “Coming soon” in the panel and is not sent at the moment.Webhooks are managed in the My Account → API Center → Security & IP → Webhook tab. The fields in the “Add new webhook” form:
| Field | Description |
|---|---|
| Key | The API key the webhook belongs to. Each API key has a single webhook; saving again for the same key replaces the existing webhook. |
| URL | The address events are sent to. Use a public https:// address (see Security). |
| Signing secret | The secret value used for the signature. It is not shown in the panel after it is saved. When editing, leaving the field empty keeps the current secret; enter a new value to change it, or tick the “Remove the current secret (send unsigned)” box to remove it. If there is no secret, requests are sent unsigned (we do not recommend this). |
| Signing algorithm | sha256 (default) or sha512. |
| Maximum retries | 0–10. Default 3. The maximum number of retries after the first attempt. |
| Retry interval (seconds) | 1–3600. Default 30. It doubles with each attempt (see Retries). |
| Trigger events | The events you want to receive. If none is selected, all events except sms.received are sent. Because sms.received carries message content, it is sent only if it is selected explicitly. |
| Webhook active | While it is off, events are recorded but not sent; pending retries are also canceled. When you turn it back on, the events from the off period are not sent. The configuration is kept. |
sms.sent, sms.delivered, sms.failed on one key's webhook, and only sms.received on the other.| Case | Webhook that receives the event |
|---|---|
| Message sent with an API key | Only that key's webhook. In the body, key_id is this key's number. |
| Message sent from the panel or by an automation | All enabled webhooks on your account that have selected the event. In the body, key_id = null. |
sms.received | All enabled webhooks on your account that have explicitly selected the sms.received event. The message must arrive at a 0850 number assigned to your account. |
inbound.matched | The first enabled webhook on your account (the one with the lowest key number). It does not depend on the event selection. |
key.test | The webhook of the key whose Test button you pressed. |
Messages sent with your account's main API key are treated like panel sends (all webhooks, key_id = null). If two webhooks on your account use the same URL, each event is sent to that URL only once.
Each request is sent with the POST method and a UTF-8 JSON body. Headers:
| Header | Description |
|---|---|
| Content-Type | Always application/json. |
| User-Agent | Fixed value: TurkeySMS-Webhook/1.0. |
| X-TurkeySMS-Event | Event name; the same as event in the body. |
| X-TurkeySMS-Delivery | Delivery ID (32 hex characters). It is the same as id in the body and does not change on retries. |
| X-TurkeySMS-Attempt | Attempt number: 1 on the first send, increased by one on each retry. |
| X-TurkeySMS-Timestamp | The moment this attempt was sent (Unix seconds). Renewed on every attempt. |
| X-TurkeySMS-Signature | Legacy signature: <algo>=hex(HMAC(secret, body)). It does not cover the timestamp; it is sent only for backward compatibility. |
| X-TurkeySMS-Signature-V2 | Recommended signature: t=<time>,v1=hex(HMAC(secret, "<time>.<body>")). If no secret is set, the signature headers are not sent. |
POST /turkeysms/webhook HTTP/1.1 Host: ornek.com Content-Type: application/json User-Agent: TurkeySMS-Webhook/1.0 X-TurkeySMS-Event: sms.delivered X-TurkeySMS-Delivery: 27f38025030d060f3e3b760b318b406e X-TurkeySMS-Attempt: 1 X-TurkeySMS-Timestamp: 1790857803 X-TurkeySMS-Signature: sha256=776ebd61c3789873d7851a30e868ff8e73dbc586bccd67d49e50b3498e827e44 X-TurkeySMS-Signature-V2: t=1790857803,v1=660a71ff372d5921231c4871913b030476acfdad1dafee4eb6f3117126837b11
The body uses the same envelope for every event:
| Field | Type | Description |
|---|---|---|
| id | string | The event's unique ID (32 hex characters) = X-TurkeySMS-Delivery. Use this value to filter out duplicates. |
| event | string | Event name (for example, sms.delivered). |
| created_at | string | The time the event was created, ISO 8601. |
| timestamp | int | The time the event was created, Unix seconds. It does not change on retries; for the signature time window, use the t= value in the signature, not this. |
| data | object | Event-specific fields (below). |
In the sms.sent, sms.delivered and sms.failed events, data contains the following common fields. The message text is not sent in these events.
| Field | Type | Description |
|---|---|---|
| message_id | int | Message number. It is the same as sms_id in the /sms/send response; it can be used with SMS status query. |
| bulk_id | string | The send's bulk operation (campaign) number. |
| to | string | Recipient number, in international format (905xxxxxxxxx). |
| sender_id | string | The Sender ID the message was sent with. |
| parts | int | How many SMS parts the message consists of. |
| source | string | The message's source. Examples: api (through the API), Web (from the panel). |
| key_id | int | null | The number of the API key the message was sent with. null for messages sent from the panel or by an automation. |
| sent_at | string | Send time, ISO 8601 (2026-10-01T15:28:31+03:00). |
sms.sent — the message was passed to the operator. Only the common fields are sent.
{ "id": "353272ae3ae66d3233cd5ebe30faadcf", "event": "sms.sent", "created_at": "2026-10-01T15:30:02+03:00", "timestamp": 1790857802, "data": { "message_id": 518431556, "bulk_id": "1259383818", "to": "905xxxxxxxxx", "sender_id": "BASLIGIM", "parts": 1, "source": "api", "key_id": 4804, "sent_at": "2026-10-01T15:28:31+03:00" } }
sms.delivered — the message was delivered to the recipient. In addition to the common fields:
| Field | Type | Description |
|---|---|---|
| status | string | Always delivered. |
| done_at | string | null | The operator's delivery time, ISO 8601. null if the operator did not report a time. |
| operator_status | string | The status text returned by the operator (for example, Message delivered to handset). |
{ "id": "27f38025030d060f3e3b760b318b406e", "event": "sms.delivered", "created_at": "2026-10-01T15:30:03+03:00", "timestamp": 1790857803, "data": { "message_id": 518431556, "bulk_id": "1259383818", "to": "905xxxxxxxxx", "sender_id": "BASLIGIM", "parts": 1, "source": "api", "key_id": 4804, "sent_at": "2026-10-01T15:28:31+03:00", "done_at": "2026-10-01T15:28:33+03:00", "operator_status": "Message delivered to handset", "status": "delivered" } }
sms.failed — the message could not be passed to the operator or could not be delivered. In addition to the common fields:
| Field | Type | Description |
|---|---|---|
| stage | string | submit: the message has still not been passed to the operator about 6–7 minutes after it was recorded (in this case bulk_id is "0" and reason may be empty). delivery: the operator returned an undelivered report. |
| status | string | rejected at the submit stage; undelivered, expired or canceled at the delivery stage. |
| reason | string | Error reason (at most 200 characters). |
| done_at | string | null | Only at the delivery stage: the operator's report time. |
| operator_status | string | Only at the delivery stage: the operator's status text. |
{ "id": "9755ae0b22863732063111ea35e2e1f4", "event": "sms.failed", "created_at": "2026-10-01T15:35:44+03:00", "timestamp": 1790858144, "data": { "message_id": 518431601, "bulk_id": "1259383818", "to": "905xxxxxxxxx", "sender_id": "BASLIGIM", "parts": 1, "source": "api", "key_id": 4804, "sent_at": "2026-10-01T15:28:31+03:00", "done_at": "2026-10-01T15:35:40+03:00", "operator_status": "Unknown Subscriber", "stage": "delivery", "status": "undelivered", "reason": "Unknown Subscriber" } }
{ "id": "b1c2d3e4f5a60718293a4b5c6d7e8f90", "event": "sms.failed", "created_at": "2026-10-01T15:40:31+03:00", "timestamp": 1790858431, "data": { "message_id": 518431620, "bulk_id": "0", "to": "905xxxxxxxxx", "sender_id": "BASLIGIM", "parts": 1, "source": "api", "key_id": 4804, "sent_at": "2026-10-01T15:28:31+03:00", "stage": "submit", "status": "rejected", "reason": "" } }
sms.received — an SMS arrived at the 0850 number assigned to your account. Because it contains the message text, it is sent only if it is selected explicitly in the webhook settings.
| Field | Type | Description |
|---|---|---|
| message_id | int | The inbound message's number. Inbound messages are numbered separately from outbound messages. |
| from | string | The sender's number. |
| to | string | Your 0850 number the message arrived at (908509xxxxxx). |
| text | string | Message text (UTF-8). |
| network | string | The sender's operator (for example, TURKCELL-TR). |
| received_at | string | The time the message was received, ISO 8601. |
{ "id": "9262804e8760b90e1c0714d0ca78ac02", "event": "sms.received", "created_at": "2026-10-01T15:49:02+03:00", "timestamp": 1790858942, "data": { "message_id": 436956, "from": "905xxxxxxxxx", "to": "908509444004", "text": "BANK", "network": "TURKCELL-TR", "received_at": "2026-10-01T15:48:48+03:00" } }
inbound.matched — the “Fire Webhook” action of a rule on the Inbound → Automation page ran. In addition to the same fields as sms.received, it contains rule_id (int) and rule_name (string).
inbound.matched event, received_at is currently sent in the YYYY-MM-DD HH:MM:SS format (Istanbul time, without time zone information). Accept both this format and the ISO 8601 format in your receiver.{ "id": "4be0f6d1c2a3958e7f60a1b2c3d4e5f6", "event": "inbound.matched", "created_at": "2026-10-01T15:52:31+03:00", "timestamp": 1790859151, "data": { "message_id": 436957, "from": "905xxxxxxxxx", "to": "908509444004", "text": "HOOK", "network": "TURKCELL-TR", "received_at": "2026-10-01 15:52:24", "rule_id": 12, "rule_name": "Webhook'a ilet" } }
key.test — sent with the Test button in the panel; it is not retried. The message value depends on your brand.
{ "id": "5d0e3c9a7b1f42e68c0d9a1b2c3e4f50", "event": "key.test", "created_at": "2026-10-01T15:01:06+03:00", "timestamp": 1790856066, "data": { "key_id": 4804, "message": "TurkeySMS test fire", "test": true } }
"simulated": true inside data. The message_id and phone numbers in these events are not real and there is no key_id field; check this field so you do not mix them with your live data.If a secret is set, each request comes with two signature headers. Verify the X-TurkeySMS-Signature-V2 header; because it also covers the timestamp, it prevents a captured request from being sent again (replay).
X-TurkeySMS-Signature-V2 value: t=<unix>,v1=<hex>. Here v1 is the version label of the signature scheme.sha256, 128 characters = sha512 (or use the one you selected in the panel).HMAC(algorithm, secret, t + "." + raw_body) as hex.hash_equals, crypto.timingSafeEqual, hmac.compare_digest).|now − t| is greater than 300 seconds, reject the request. Make sure your server clock is synchronized with NTP.401 and do not process the body.timestamp field in the body. This field is the event's creation time and does not change on retries; it would make you reject a valid retry that arrives hours later. Each attempt is re-signed with a new t=.Legacy signature (X-TurkeySMS-Signature): it has the sha256=<hex> format and signs only the body. Because it contains no timestamp, on its own it gives no protection against replay attacks. Do not use it in new integrations.
Changing the secret: As soon as the new secret is saved in the panel, all later attempts are signed with the new secret. To avoid downtime, first make your receiver accept both the old and the new secret, then change the secret in the panel, and a few hours later remove the old secret from your receiver.
| Your server's response | What TurkeySMS does |
|---|---|
| 2xx | The event was delivered; it is not sent again. |
| 408, 429, 5xx | Temporary error; it is retried. |
| Connection error, DNS error, TLS error, 10-second timeout | Temporary error; it is retried. |
| 3xx (redirect) | Redirects are not followed; permanent error, not retried. Use the URL's final address. |
| Other 4xx (400, 401, 403, 404 …) | Permanent error; not retried. |
The connection time is at most 5 seconds, and the total request time is at most 10 seconds. The wait time doubles with each attempt: interval × 2(attempt − 1), at most 6 hours. With the default settings (3 retries, 30 seconds):
| Attempt | When | Note |
|---|---|---|
| 1 | When the event is created | |
| 2 | About 30 s after attempt 1 | |
| 3 | About 60 s after attempt 2 | |
| 4 | About 120 s after attempt 3 | Last attempt; if it fails, the event is marked as exhausted. |
Retries run on a 30-second processing cycle; the actual time may be up to about 30 seconds longer than in the table. All attempts (request, response code, the first 8,000 characters of the response body, duration) appear on the Webhook Center → Delivery Logs page. For this reason, do not return secret information in the response body; a short value (ok) is enough.
200 right away. Do long operations (email, external API calls and so on) after the response. A response that takes longer than 10 seconds counts as a timeout even if your server processed the event, and the event is sent again.id value from the body, and if an id you have already processed arrives, return 200 without processing it.sms.delivered may arrive before sms.sent. Keep the message status per message_id; delivered and failed are final states, and an sms.sent that arrives later must not change them.id record and return 500; the event is retried.200 also when an event you do not handle arrives; otherwise unnecessary retries occur.localhost, private networks (10.x, 172.16–31.x, 192.168.x), CGNAT and reserved addresses are not accepted. URLs that contain a private or reserved IP address or a local name (localhost, .local, .lan, .internal) are rejected when saved; domain names that resolve to a private address are rejected at send time, and this error is not retried. The domain name is resolved on every send, and the connection is made to the verified IP address.http → https or adding a trailing / are not followed; enter the final address directly.401.sms.received and inbound.matched contain the message text and phone number; store this data in line with KVKK (the Turkish Personal Data Protection Law) and limit access to it.| Tool | Where | What it does |
|---|---|---|
| Test button | Security & IP → Webhook → Configured webhooks | Sends a key.test event and shows the result (HTTP code, duration) immediately. It is not retried. |
| Webhook Simulator | The “Sandbox” card at the bottom of the same page | Sends the event you choose (sms.sent, sms.delivered, sms.failed, sms.received, key.test) with a real signature and real retries; the body contains "simulated": true. If the webhook has not selected that event or is not active, nothing is sent. When “Dry-run” is ticked, no request is sent; only the body and the signature are shown. |
| Delivery Logs | API Center → Webhook Center | Shows the request and response of each attempt, the attempt number, the duration and the error reason; can be filtered by key, event and status, and downloaded as CSV. |
| Health & Alerts | API Center → Webhook Center | The number of consecutive failures, the success rate for the last 24 hours, the last success/failure time, risky webhooks and the retry status. |
localhost) cannot be used directly. Test through a public test server or an HTTPS tunnel service.All three examples do the same job: they verify the V2 signature and the time window, filter out duplicates by id, and return 200 quickly. The examples have been tested with the valid request, retry, wrong secret, stale timestamp, tampered body and sha512 scenarios. For the duplicate check, you can also use a UNIQUE column in a database instead of a file; for critical operations, save the id in the same database transaction as the event.
<?php
// TurkeySMS webhook receiver (PHP 7.4+)
$secret = getenv('TURKEYSMS_WEBHOOK_SECRET') ?: ''; // the secret you set in the panel (from an environment variable)
$seenDir = '/var/lib/myapp/webhook-seen'; // a writable folder outside the web root
$body = file_get_contents('php://input'); // the signature is computed over the RAW body
if ($secret === '') { // configuration missing: let TurkeySMS retry
http_response_code(500);
exit;
}
// 1) Verify X-TurkeySMS-Signature-V2: t=<unix>,v1=<hex HMAC("<t>.<body>")>
$sig = $_SERVER['HTTP_X_TURKEYSMS_SIGNATURE_V2'] ?? '';
if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64}|[0-9a-f]{128})$/D', $sig, $m)) {
http_response_code(401);
exit;
}
$algo = strlen($m[2]) === 64 ? 'sha256' : 'sha512'; // the algorithm you selected in the panel
$expected = hash_hmac($algo, $m[1] . '.' . $body, $secret);
// The time window is measured with t= in the signature (each attempt is re-signed), not with "timestamp" in the body.
if (!hash_equals($expected, $m[2]) || abs(time() - (int)$m[1]) > 300) {
http_response_code(401);
exit;
}
// 2) Filter out duplicates: "id" = X-TurkeySMS-Delivery, the same on every attempt.
$evt = json_decode($body, true);
$id = is_array($evt) && preg_match('/^[0-9a-f]{32}$/D', (string)($evt['id'] ?? '')) ? $evt['id'] : '';
if ($id === '') {
http_response_code(400);
exit;
}
if (!is_dir($seenDir)) @mkdir($seenDir, 0750, true);
$marker = @fopen($seenDir . '/' . $id, 'x'); // fails if the same id arrived before
if ($marker === false) {
http_response_code(is_file($seenDir . '/' . $id) ? 200 : 500); // 500 → TurkeySMS retries
exit;
}
fclose($marker);
// 3) Save or queue the event quickly (TurkeySMS waits at most 10 seconds).
// If saving fails, delete the marker file and return 500; the event is retried.
$d = $evt['data'];
switch ($evt['event']) {
case 'sms.sent': /* $d['message_id'], $d['to'], $d['sender_id'] */ break;
case 'sms.delivered': /* $d['message_id'], $d['done_at'] */ break;
case 'sms.failed': /* $d['message_id'], $d['stage'], $d['status'], $d['reason'] */ break;
case 'sms.received': /* $d['from'], $d['to'], $d['text'] */ break;
case 'inbound.matched': /* $d['from'], $d['text'], $d['rule_id'] */ break;
case 'key.test': break;
}
http_response_code(200);
echo 'ok';// TurkeySMS webhook receiver (Node.js 18+ · Express 4)
const express = require('express');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const SECRET = process.env.TURKEYSMS_WEBHOOK_SECRET; // the secret you set in the panel
const SEEN_DIR = '/var/lib/myapp/webhook-seen'; // a writable, persistent folder
fs.mkdirSync(SEEN_DIR, { recursive: true });
const app = express();
// The signature is computed over the RAW body: do not use a JSON parser on this route.
app.post('/turkeysms/webhook', express.raw({ type: '*/*', limit: '256kb' }), (req, res) => {
if (!SECRET) return res.sendStatus(500); // configuration missing: let TurkeySMS retry
const body = req.body; // Buffer
const m = /^t=(\d+),v1=([0-9a-f]{64}|[0-9a-f]{128})$/.exec(req.get('X-TurkeySMS-Signature-V2') || '');
if (!m) return res.sendStatus(401);
const algo = m[2].length === 64 ? 'sha256' : 'sha512';
const expected = crypto.createHmac(algo, SECRET).update(m[1] + '.').update(body).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(m[1])) <= 300;
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2])) || !fresh) {
return res.sendStatus(401);
}
let evt;
try { evt = JSON.parse(body.toString('utf8')); } catch { return res.sendStatus(400); }
if (!/^[0-9a-f]{32}$/.test(evt.id || '')) return res.sendStatus(400);
// Filter out duplicates: "id" is the same on every attempt.
try {
fs.writeFileSync(path.join(SEEN_DIR, evt.id), '', { flag: 'wx' });
} catch (e) {
return res.sendStatus(e.code === 'EEXIST' ? 200 : 500);
}
// Save or queue the event quickly (at most 10 seconds).
const d = evt.data;
switch (evt.event) {
case 'sms.sent': /* d.message_id, d.to */ break;
case 'sms.delivered': /* d.message_id, d.done_at */ break;
case 'sms.failed': /* d.stage, d.status, d.reason */ break;
case 'sms.received': /* d.from, d.to, d.text */ break;
case 'inbound.matched': /* d.text, d.rule_id */ break;
case 'key.test': break;
}
res.status(200).send('ok');
});
app.listen(3000);# TurkeySMS webhook receiver (Python 3.8+ · Flask 2+)
import hashlib, hmac, json, os, re, time
from flask import Flask, request
SECRET = os.environ["TURKEYSMS_WEBHOOK_SECRET"].encode() # the secret you set in the panel
SEEN_DIR = "/var/lib/myapp/webhook-seen" # a writable, persistent folder
os.makedirs(SEEN_DIR, exist_ok=True)
SIG_RE = re.compile(r"t=(\d+),v1=([0-9a-f]{64}|[0-9a-f]{128})")
app = Flask(__name__)
@app.post("/turkeysms/webhook")
def turkeysms_webhook():
body = request.get_data() # RAW body (bytes)
m = SIG_RE.fullmatch(request.headers.get("X-TurkeySMS-Signature-V2", ""))
if not m:
return "", 401
ts, sig = m.group(1), m.group(2)
algo = hashlib.sha256 if len(sig) == 64 else hashlib.sha512
expected = hmac.new(SECRET, ts.encode() + b"." + body, algo).hexdigest()
if not hmac.compare_digest(expected, sig) or abs(time.time() - int(ts)) > 300:
return "", 401
try:
evt = json.loads(body)
except ValueError:
return "", 400
eid = evt.get("id", "") if isinstance(evt, dict) else ""
if not re.fullmatch(r"[0-9a-f]{32}", eid):
return "", 400
# Filter out duplicates: "id" is the same on every attempt.
try:
os.close(os.open(os.path.join(SEEN_DIR, eid), os.O_CREAT | os.O_EXCL | os.O_WRONLY))
except FileExistsError:
return "duplicate", 200
except OSError:
return "", 500 # TurkeySMS retries
# Save or queue the event quickly (at most 10 seconds).
d = evt["data"]
if evt["event"] == "sms.delivered":
pass # d["message_id"], d["done_at"]
elif evt["event"] == "sms.failed":
pass # d["stage"], d["status"], d["reason"]
elif evt["event"] == "sms.received":
pass # d["from"], d["to"], d["text"]
return "ok", 200| Symptom | Possible cause | Fix |
|---|---|---|
| 401 in Delivery Logs | The secret is not the same as in the receiver; the body was changed before the signature was verified (the JSON was parsed and rebuilt); the server clock has drifted. | Enter the secret again on both sides; compute the signature over the raw body; synchronize the clock with NTP. |
| “Timeout” | The receiver does not respond within 10 seconds. | Return 200 first and do the work afterwards. The event is sent again; filter out the duplicate by id. |
| 3xx and no retry | The URL redirects (for example, http → https, trailing /). | Enter the final address, with no redirect, as the webhook URL. |
| No events arrive | The webhook is not active; the event is not selected; the message was sent with another API key; the URL is on a private network. | Check the settings and the routing rules; verify the connection with the Test button. |
sms.received does not arrive | The event was not selected explicitly (an empty selection does not include this event), or the message arrived at a number that is not assigned to your account. | Tick the sms.received event on the webhook; send the message to a number on the Inbound → My Numbers page. |
inbound.matched arrived at an unexpected URL | This event always goes to the first enabled webhook on your account. | Make sure your first webhook can handle this event (if it does not process it, it should still return 200). |
| The same event arrived twice | At-least-once delivery; a retry after a timeout. | This is normal. Filter out duplicates by id. |
sms.delivered did not arrive | The event is not selected, or the operator did not return a report within 72 hours. | Check the webhook events; query the status with SMS status query. |
| Events arrive late | When your endpoint returns a connection error, 429 or 5xx, all pending events of that webhook are delayed by 60 seconds; if the error continues, the attempts are exhausted. | Review the error on the Delivery Logs and Health & Alerts pages and fix your endpoint. |
https:// address with a valid certificate and does not redirect.X-TurkeySMS-Signature-V2 signature over the raw body with a constant-time comparison.t= in the signature (300 s), and the server clock is synchronized with NTP.id; the status is kept per message_id, and final states are preserved.2xx in less than 10 seconds; long jobs are queued.200 is also returned for unrecognized events.sms.received is needed, it is ticked explicitly.Can I send events to more than one URL?
Yes. Each API key has one webhook; use different keys for different URLs. Events for messages sent from the panel go to all enabled webhooks.
Which time zone are events in?
Date fields are sent in ISO 8601 format with time zone information (+03:00); for received_at in inbound.matched, see the note above. timestamp and X-TurkeySMS-Timestamp are Unix seconds.
Is the message text included in the webhook?
Not in outbound message events (sms.sent, sms.delivered, sms.failed). It is included for inbound messages (sms.received, inbound.matched).
Can I receive events from the period when the webhook was off?
No. Events from the off period are not sent; query the message statuses for that period with SMS reports.
What happens when the retries are exhausted?
The event is marked as exhausted and is not sent again. You can see it in Delivery Logs.
You can monitor your API usage in the panel:
| Panel | Content |
|---|---|
| API Center → Statistics | Total calls, success rate, distribution by endpoint and the most used keys. |
| API Center → Connection Logs | Request records; can be filtered by status, endpoint, key, IP and response code. |
| Webhook / Logs → Delivery Logs | Delivery records of webhook requests and their responses. |
Some requests rejected during parameter validation (for example, a missing field) may not appear in the connection logs. When debugging, we recommend that you also log requests and responses (excluding the API key) on your side.
Codes may be returned with different HTTP statuses depending on the endpoint. Detailed descriptions are in the related endpoint section; this table is for quick reference.
| Code | HTTP | Meaning |
|---|---|---|
| SRV-ERR | 500 | Unexpected server error. Balance query and Sender ID query may also return this code with 403. |
| TS-1033 | 400 404 | Invalid body; in Groups, Numbers and Reports, wrong path with 404. |
| TS-1030 | 400 403 | The account is not active. |
| TS-1031 | 400 401 403 | The API key is invalid, was not found or is not active. |
| TS-1035 | 403 | The key is paused, expired or revoked. |
| TS-1066 | 403 | The request's IP address is not on the key's allowlist. |
| TS-1068 | 429 | The key's hourly request limit has been reached. |
| TS-1069 | 429 | The key's daily request limit has been reached. |
| TS-1073 | 429 | The key's monthly request limit has been reached. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1000 | 200 | The key is valid; permissions and the account summary were returned. |
| TS-5000 | 500 | Unexpected server error. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1024 | 200 | The send was accepted. |
| TS-1050 | 401 | api_key is missing or short. |
| TS-1025 | 400 | Recipient is missing. |
| TS-1051 | 400 | Sender ID is missing. |
| TS-1029 | 400 | Sender ID is longer than 11 characters. |
| TS-1026 | 400 | Text is empty or longer than 2,000 characters. |
| TS-1028 | 400 | The Sender ID is not on the account or is not approved. |
| TS-1060 | 400 429 | 400: recipient count limit. 429: per-minute send limit. |
| TS-1061 | 403 | “Allow POST requests” is off. |
| TS-1062 | 403 | “Send SMS” is off. |
| TS-1027 | 403 | Insufficient balance. |
| TS-1070 / TS-1071 / TS-1072 | 400 | Scheduling date, time or a time in the past. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1024 | 200 | The send was queued. |
| TS-1025 | 400 | api_key is missing or short. |
| TS-1029 | 400 | Sender ID is missing. |
| TS-1026 | 400 | The number list is missing or a text is longer than 2,000 characters. |
| TS-1060 | 400 | 50,000 number limit. |
| TS-1070 / TS-1071 / TS-1072 | 400 | The scheduling fields are invalid. |
| TS-1067 | 403 | “Send to group” is off. |
| TS-1028 | 403 | The Sender ID is not on the account or is not approved. |
| TS-1027 | 403 | Insufficient balance. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1024 | 200 | The OTP was sent. |
| TS-1050 | 400 401 | api_key is missing. |
| TS-1025 | 400 | Number is missing. |
| TS-1034 | 400 403 | The number format is invalid. |
| TS-1051 | 400 | Sender ID is missing (Advanced OTP). |
| TS-1026 | 400 | Text is empty, has no TS-CODE or is too long (Advanced OTP). |
| TS-1029 | 400 403 | Sender ID is longer than 11 characters or not on the account (Advanced OTP). |
| TS-1028 | 403 | The Sender ID is not approved for OTP (Advanced OTP). |
| TS-1036 | 403 | “Send OTP” is off. |
| TS-1037 | 403 | “Advanced OTP” is off. |
| TS-1061 | 403 | “Allow POST requests” is off (Advanced OTP). |
| TS-1027 | 403 | Insufficient balance. |
| TS-5000 | 403 | The message could not be saved; retry. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1080 / TS-1087 / TS-1088 / TS-1090 | 200 | Created / updated / deleted / listed. |
| TS-1050 | 400 | api_key is missing. |
| TS-1081 / TS-1084 / TS-1085 / TS-1089 | 400 | The related permission is off. |
| TS-1082 | 200 | The group name already exists (failed result). |
| TS-1083 | 400 200 | The group name is invalid. |
| TS-1086 | 400 200 | The group was not found or group_id is missing. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1100 | 200 | The number was added. |
| TS-1101 | 200 | The number is invalid (failed result). |
| TS-1050 | 400 | api_key is missing. |
| TS-1025 | 400 | gsm_number is missing. |
| TS-1065 | 400 | “Add number” is off. |
| TS-1086 | 400 200 | The group was not found or group_id is invalid. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1141 | 200 | The number was added to the list. |
| TS-1142 / TS-1143 | 200 | The number is not on the list / on the list. |
| TS-1050 | 403 | api_key is missing. |
| TS-1025 | 400 | number is missing. |
| TS-1144 | 400 | The number is not a valid Türkiye mobile number. |
| TS-1140 | 400 | The number is already on the list. |
| TS-1065 | 403 | “Block number” is off. |
| TS-404 | 404 | Wrong path. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1040 | 200 | The query succeeded. |
| TS-1050 | 400 | api_key is missing. |
| TS-1025 | 400 | api_key is short (Balance query). |
| TS-1065 | 403 | “Check balance” is off. |
| TS-1038 | 403 | “Check Sender ID” is off. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1064 | 200 | The message was delivered. |
| TS-1022 | 200 | No delivery confirmation (not delivered or the report has not arrived yet). |
| TS-1050 | 403 | api_key is missing. |
| TS-1052 | 403 | sms_id is missing or invalid. |
| TS-1061 | 403 | “Allow POST requests” is off. |
| TS-1063 | 403 | “Check SMS status” is off. |
| TS-1020 | 403 | The message was not found. |
| Code | HTTP | Meaning |
|---|---|---|
| TS-1064 | 200 | The report was returned. |
| TS-1029 | 400 200 | The report ID is missing or the report was not found. |
| TS-1050 | 400 | api_key is missing. |
| TS-1061 | 400 | “Allow POST requests” is off. |
| TS-1063 | 400 | “Check SMS status” is off. |
The examples on this page need no library; they work with the standard HTTP client in each language. Set a timeout in your own code and evaluate the result by the result (or status) and result_code fields.
To try the API from your browser, you can use the interactive documentation (Swagger, OpenAPI 3.1).
result_code, and the date and time of the request; do not share your API key.