REST API — совместимо с handler_api

Документация API SMS-подтверждения

Подключите Turonay к своему ПО, боту или панели. С одним API-ключом вы можете получить номер, прочитать входящий SMS-код и отменить активацию. API совместим с протоколом handler_api: если у вас уже есть интеграция под этот протокол, обычно достаточно сменить базовый адрес и ключ. Вместо опроса кода можно получать подписанные уведомления webhook.

11
Эндпоинта — освоить за 5 минут
GET
Один метод, ответы: текст + JSON
Возврат
Нет кода — нет оплаты

Быстрый старт

Первый номер арендуется в три шага. Откройте панель и создайте API-ключ, затем выполните запрос ниже.

# 1) Проверьте баланс curl "https://turonay.com/api/?api_key=YOUR_KEY&action=getBalance" # → ACCESS_BALANCE:125.4000 # 2) Арендуйте номер (код сервиса + id страны) curl "https://turonay.com/api/?api_key=YOUR_KEY&action=getNumber&service=wa&country=62" # → ACCESS_NUMBER:184213:628123456789 # 3) Дождитесь кода (опрос каждые несколько секунд) curl "https://turonay.com/api/?api_key=YOUR_KEY&action=getStatus&id=184213" # → STATUS_WAIT_CODE … затем → STATUS_OK:123456

Классические методы возвращают обычный текст (поля через двоеточие, формат handler_api); методы V2 и справочные возвращают JSON. Вместо постоянного опроса кода можно использовать webhook.

Базовый адрес и аутентификация

Все запросы отправляются на один из двух равнозначных адресов ниже методом GET; ответы одинаковые:

https://turonay.com/api/ https://turonay.com/stubs/handler_api.php

Переходите от другого провайдера, использующего протокол handler_api? В большинстве интеграций достаточно сменить базовый адрес и API-ключ: путь /stubs/handler_api.php следует распространённому соглашению handler_api.php; параметры и форматы ответов следуют тому же протоколу, есть несколько известных отличий.

Аутентификация выполняется параметром api_key. Заголовки не используются. Если не хотите, чтобы ключ попадал в URL и журналы сервера, параметры можно передавать и полями формы через POST (application/x-www-form-urlencoded). Ключ можно пересоздать в панели в любой момент — старый сразу перестаёт работать.

Для доступа к API нужны пополнения на сумму не менее 100,00 ₺ и подтверждённый адрес электронной почты. Если условие не выполнено, каждый запрос возвращает ошибку, начинающуюся с API_ACCESS_DENIED (см. таблицу ошибок ниже).

Не используйте ключ на стороне клиента. Ключ, встроенный в код в браузере, позволяет кому угодно тратить ваш баланс — выполняйте вызовы со своего сервера.

Ограничение запросов

API ограничен по ключу и по IP в пределах минуты. Запросы сверх лимита возвращают HTTP 429 и NO_CONNECTION в теле ответа.

Не опрашивайте статус раз в секунду. Рекомендуемый интервал — 3–5 секунд; получив 429, увеличивайте паузу вдвое (5с, 10с, 20с).

Лимит по умолчанию — 60 запросов в минуту на ключ и IP. Чтобы оставаться в его пределах, используйте вебхуки вместо частого опроса.

Оплата

Оплата списывается только тогда, когда SMS-код дошёл до вас. При получении номера сумма резервируется на балансе (getBalance показывает уменьшенный доступный баланс); окончательно она списывается только при доставке кода. Если вы отмените активацию до прихода кода или время истечёт, платить не нужно.

При запросе номера баланса должно хватать на цену, иначе вернётся NO_BALANCE. После прихода кода активацию отменить нельзя (setStatus&status=8): вернётся EARLY_CANCEL_DENIED, деньги не возвращаются, так как услуга оказана. Если поставщик отменит номер или время истечёт до прихода кода, зарезервированная сумма возвращается на баланс.

Эндпоинты

actionПараметрыУспешный ответОписание
getPricesservice, country (необязательно){"62":{"wa":{"cost":12.50,"count":744716}}}Цена и наличие по доступным парам сервис/страна. cost — сумма, которая спишется с баланса, уже с вашей скидкой уровня. Цена ненадолго фиксируется: даже если сменится вышестоящий сервер, спишется то, что вы видели.
getCountries—{"62":{"id":62,"eng":"Turkey"}}Страны, в которых вы действительно можете купить номер. Перечислены только страны с реальным наличием.
getNumbersStatuscountry{"wa_0":"744716"}Наличие по сервисам для одной страны. Формат ключа — service_0.
getBalance—ACCESS_BALANCE:<сумма>Возвращает доступный баланс.
getOperatorscountry (необязательно){"status":"success","countryOperators":{"62":["any"]}}Доступные операторы по странам. Одно из значений передаётся в параметре operator методов getNumber / getNumberV2.
getNumberservice, country, operator (необязательно), maxPrice (необязательно)ACCESS_NUMBER:<id>:<телефон>Выдаёт номер. Возвращённый id используется в следующих вызовах. Если передать maxPrice, номер не будет взят при цене выше этого значения.
getNumberV2service, country, operator (необязательно), maxPrice (необязательно){"activationId":"184213","phoneNumber":"628123456789",…}То же, что getNumber, но ответ в JSON и включает цену, срок и оператора.
getStatusidSTATUS_WAIT_CODE / STATUS_WAIT_RETRY:<код> / STATUS_OK:<код> / STATUS_CANCELСпрашивает, пришёл ли SMS-код. Оплата окончательно списывается в момент прихода кода.
getStatusV2id{"sms":{"code":"123456",…}}JSON-аналог getStatus. Когда код пришёл, возвращается JSON с объектом sms; пока кода нет, возвращается обычный текстовый статус (STATUS_WAIT_CODE и т. д.), а не JSON.
getActiveActivations—{"status":"success","activeActivations":[…]}Список открытых (не истёкших / не закрытых) активаций вашего аккаунта. Используйте для продолжения работы после обрыва связи или перезапуска.
setStatusid, status (1 / 3 / 6 / 8)ACCESS_READY / ACCESS_RETRY_GET / ACCESS_ACTIVATION / ACCESS_CANCEL1 готово, 3 запросить ещё SMS (бесплатно), 6 завершить (только после прихода кода), 8 отменить (запрещено, если код уже пришёл).

getNumber — аренда номера

service — короткий код сервиса (например, wa для WhatsApp), country — id страны. Актуальные коды и id смотрите на странице цен.

Необязательно: operator запрашивает конкретного оператора (getOperators); maxPrice — максимальная цена, которую вы согласны платить; если текущая цена выше, номер не берётся.

GET ?action=getNumber&service=wa&country=62&maxPrice=15 ACCESS_NUMBER:184213:628123456789 │ └─ телефон └─ id заказа NO_NUMBERS → сейчас нет в наличии NO_BALANCE → недостаточно средств BAD_SERVICE → неизвестный код сервиса / не продаётся WRONG_MAX_PRICE:18.40 → maxPrice неверен или не покрывает текущую цену; покупка не выполняется BAD_KEY → неверный API-ключ

При получении номера цена резервируется на балансе (getBalance показывает уменьшенный доступный баланс); окончательно оплата списывается только при приходе SMS-кода. Если активация отменена или истекла без кода, резерв снимается. Баланса должно хватать на цену.

getStatus — чтение кода

Значение id приходит из вызова getNumber.

GET ?action=getStatus&id=184213 STATUS_WAIT_CODE → код ещё не пришёл STATUS_OK:123456 → код получен STATUS_WAIT_RETRY:123456 → вы запросили ещё SMS (setStatus&status=3), а предыдущий код ещё показывается; при приходе нового кода статус станет STATUS_OK STATUS_CANCEL → отменено / истекло

Чтобы получать уведомление о приходе кода без опроса, используйте Webhooks (обычно доставляется в течение 1–2 минут). Получив код, рекомендуем закрыть активацию запросом setStatus&status=6.

getNumberV2 — получение номера в JSON

Принимает те же параметры, что и getNumber, при успехе возвращает JSON. При ошибке приходят классические текстовые коды (NO_NUMBERS, NO_BALANCE, WRONG_MAX_PRICE:<цена> ...).

GET ?action=getNumberV2&service=wa&country=62 { "activationId": "184213", "phoneNumber": "628123456789", "activationCost": "12.50", "currency": 949, "countryCode": "62", "canGetAnotherSms": true, "activationTime": "2026-10-07 12:00:00", "activationEndTime": "2026-10-07 12:20:00", "activationOperator": "any" }
fieldОписание
activationIdИдентификатор активации; используется как id в вызовах getStatus / setStatus.
phoneNumberНомер телефона (с кодом страны, без знака +).
activationCostЦена (строкой, например "12.50"); сумма, которая окончательно спишется, если придёт код.
currencyВалюта: числовой код ISO-4217 (целое число; например, 949 = TRY).
countryCodeИдентификатор страны.
canGetAnotherSmsМожно ли запросить ещё одно SMS на этот номер (setStatus&status=3).
activationTimeВремя начала активации (время сервера, Europe/Istanbul, UTC+3; формат Y-m-d H:i:s, без часового пояса).
activationEndTimeВремя окончания срока номера; если код не пришёл, после этого активация отменяется. Тот же формат и часовой пояс, что у activationTime.
activationOperatorИспользованный оператор (any = без предпочтений).

getStatusV2 — статус в JSON

Значение id возвращается вызовом getNumber. Когда код пришёл, возвращается JSON: объект sms содержит dateTime (ISO-8601), code и text; call сейчас всегда null. Пока кода нет, возвращается обычный текстовый статус, а не JSON (STATUS_WAIT_CODE, STATUS_WAIT_RETRY:..., STATUS_CANCEL).

GET ?action=getStatusV2&id=184213 // когда код пришёл: JSON { "verificationType": 0, "sms": { "dateTime": "2026-10-07T12:00:41+03:00", "code": "123456", "text": "Your code: 123456" }, "call": null } // пока кода нет: обычный текстовый статус, не JSON STATUS_WAIT_CODE

getActiveActivations — открытые активации

Возвращает открытые активации вашего аккаунта в JSON (идентификатор, сервис, номер, сумма, статус и полученные коды). activationStatus: "4" ожидание кода, "1" код получен, "3" ожидание повторного SMS. Надёжный признак прихода кода — массив smsCode; не полагайтесь только на поле статуса. Используйте при перезапуске приложения, чтобы подхватить незавершённые операции.

GET ?action=getActiveActivations { "status": "success", "activeActivations": [ { "activationId": "184213", "serviceCode": "wa", "phoneNumber": "628123456789", "activationCost": "12.50", "activationStatus": "1", "smsCode": ["123456"], "countryCode": "62" } ] }

setStatus — смена статуса

Пример: GET ?action=setStatus&id=184213&status=6. Допускаются четыре статуса:

statusУспешный ответОписание
1ACCESS_READYНомер готов / ожидается SMS (информационный).
3ACCESS_RETRY_GETЗапросить ещё SMS на тот же номер. Бесплатно; при приходе нового кода статус переходит между STATUS_WAIT_RETRY и STATUS_OK.
6ACCESS_ACTIVATIONЗавершить и закрыть. Допустимо только после прихода кода; без кода вернётся STATUS_WAIT_CODE (или BAD_STATUS), активация не завершается.
8ACCESS_CANCELОтменить активацию. Если код не приходил, активация отменяется, резерв снимается. Если код пришёл, вернётся EARLY_CANCEL_DENIED; плата окончательна и не возвращается.

Коды ошибок

КодЗначениеЧто делать
BAD_KEYAPI-ключ пуст или неверен.Проверьте ключ в панели; если пересоздали — обновите код.
BAD_ACTIONНеизвестное значение action.Допустимы только значения action из этого документа.
BAD_SERVICEСервис/страна не указаны или закрыты.Проверьте параметры и статус в прайс-листе.
NO_BALANCEНедостаточно средств или ниже минимума.Пополните баланс и попробуйте снова.
NO_NUMBERSНет номеров для этого сервиса/страны сейчас.Выберите другую страну или повторите позже.
NO_ACTIVATIONУказанный id вам не принадлежит.Убедитесь, что id взят из ответа getNumber.
BAD_STATUSНедопустимый статус для setStatus.Принимаются только 1, 3, 6 и 8.
NO_CONNECTIONВременная ошибка или лимит (HTTP 429).Подождите и повторите, постепенно увеличивая паузу.
WRONG_MAX_PRICE:<цена>Переданный maxPrice неверен или не покрывает текущую цену; покупка не выполняется.Если цена из ответа вас устраивает, передайте её как maxPrice либо повторите запрос без maxPrice.
EARLY_CANCEL_DENIEDАктивацию нельзя отменить (setStatus&status=8), так как код уже пришёл.Услуга оказана; плата не возвращается. Закройте активацию через status=6.
API_ACCESS_DENIED:MIN_DEPOSIT:<сумма>Ключ действителен, но сумма пополнений аккаунта ниже требуемой для доступа к API (сумма в TRY указана в ответе).Пополните баланс в панели; как только сумма пополнений достигнет этого значения, запросы сразу заработают. Менять ключ не нужно.
API_ACCESS_DENIED:EMAIL_NOT_VERIFIEDКлюч действителен, но адрес электронной почты аккаунта не подтверждён.Запросите письмо для подтверждения на странице профиля и перейдите по ссылке; после подтверждения запросы сразу заработают.

Пример на PHP

<?php $key = 'YOUR_KEY'; $base = 'https://turonay.com/api/'; function tur(string $base, array $q): string { return trim((string)file_get_contents( $base . '?' . http_build_query($q) )); } // Арендовать номер $r = tur($base, ['api_key'=>$key, 'action'=>'getNumber', 'service'=>'wa', 'country'=>'62']); if (!str_starts_with($r, 'ACCESS_NUMBER')) { exit($r); } [, $id, $phone] = explode(':', $r); // Ожидание кода for ($i = 0; $i < 40; $i++) { sleep(4); $s = tur($base, ['api_key'=>$key, 'action'=>'getStatus', 'id'=>$id]); if (str_starts_with($s, 'STATUS_OK')) { echo explode(':', $s)[1]; break; } if ($s === 'STATUS_CANCEL') { break; } }

Пример на Python

import time, requests KEY = "YOUR_KEY" BASE = "https://turonay.com/api/" def tur(**q): q["api_key"] = KEY return requests.get(BASE, params=q, timeout=20).text.strip() # Арендовать номер r = tur(action="getNumber", service="wa", country="62") if not r.startswith("ACCESS_NUMBER"): raise SystemExit(r) _, oid, phone = r.split(":") print("number:", phone) # Ожидание кода for _ in range(40): time.sleep(4) s = tur(action="getStatus", id=oid) if s.startswith("STATUS_OK"): print("code:", s.split(":")[1]); break if s == "STATUS_CANCEL": break

Webhooks (уведомления)

Когда приходит код или активация отменяется, мы отправляем на ваш сервер подписанный HTTPS POST, поэтому постоянно опрашивать getStatus не нужно. Адрес и секрет задаются на странице вашего профиля; там же можно отправить тестовое событие и увидеть последние 10 доставок.

eventОписание
code_receivedДля активации пришёл SMS-код (оплата окончательно списывается в этот момент). При дополнительных SMS (status=3) каждый новый код приходит отдельным событием.
cancelledАктивация была отменена поставщиком до прихода кода либо вы отменили её через setStatus&status=8; резерв снимается, оплата не списана. О тихом истечении срока уведомление не отправляется: для активаций, у которых вышло время, следите за activationEndTime или запрашивайте getStatus.
testПриходит, когда вы нажимаете «Отправить тест» в профиле; содержит примерные значения.

Требования к адресу: только https://, порт 443 или 8443, не более 500 символов, без имени пользователя/пароля, общедоступное доменное имя (IP-адреса, localhost и адреса частных сетей отклоняются). Перенаправления (3xx) не выполняются; нужен действительный TLS-сертификат.

Тело запроса

Тело — JSON. В событии cancelled поля code и text равны null.

POST https://your-server.example.com/turonay-webhook Content-Type: application/json { "event": "code_received", "activationId": "184213", "phone": "628123456789", "service": "wa", "country": "62", "code": "123456", "text": "Your code: 123456", "receivedAt": "2026-10-07T12:00:41Z", "sentAt": "2026-10-07T12:00:42Z", "id": "dlv_9f2c4a7d1b3e5f60a8c2d4e6" }

code заполнено только в событии code_received; text — текст SMS от оператора (может быть пустым). id уникален для каждой доставки и не меняется при повторах: используйте его для дедупликации.

Заголовки

headerОписание
Content-Typeapplication/json
User-AgentTuronay-Webhook/1.0
X-Turonay-EventИмя события (code_received, cancelled, test).
X-Turonay-TimestampВремя отправки, Unix-секунды. Входит в подпись.
X-Turonay-Signaturesha256= + HMAC-SHA256 подпись в шестнадцатеричном виде (см. ниже).
X-Turonay-DeliveryИдентификатор доставки (совпадает с id в теле).

Проверка подписи

Чтобы убедиться, что запрос пришёл от нас, вычислите HMAC_SHA256(secret, timestamp + "." + rawBody) и сравните с значением заголовка X-Turonay-Signature (начинается с sha256=) сравнением за постоянное время. Секрет (whsec_...) создаётся на странице профиля и показывается полностью только в момент создания.

Используйте необработанное тело (полученные байты); не разбирайте и не сериализуйте JSON заново. Отклоняйте запрос, если метка времени старше 5 минут (защита от повтора). Если секрет мог утечь, сгенерируйте новый в профиле; старый перестанет работать сразу.

Проверка на PHP

<?php $secret = 'whsec_...'; // секрет из вашего профиля $raw = file_get_contents('php://input'); // СЫРОЕ тело, а не разобранный JSON $ts = $_SERVER['HTTP_X_TURONAY_TIMESTAMP'] ?? ''; $sig = $_SERVER['HTTP_X_TURONAY_SIGNATURE'] ?? ''; // отклонять старые запросы (5 мин) if (abs(time() - (int)$ts) > 300) { http_response_code(400); exit; } $expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw, $secret); if (!hash_equals($expected, $sig)) { http_response_code(401); exit; } $e = json_decode($raw, true); if ($e['event'] === 'code_received') { // $e['activationId'], $e['code'] ... } http_response_code(200); // верните 2xx в течение 5 с

Проверка на Node.js

const crypto = require('crypto'); const express = require('express'); const app = express(); const SECRET = process.env.TURONAY_WEBHOOK_SECRET; // СЫРОЕ тело, а не разобранный JSON app.post('/turonay-webhook', express.raw({ type: 'application/json' }), (req, res) => { const ts = req.get('X-Turonay-Timestamp') || ''; const sig = req.get('X-Turonay-Signature') || ''; if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400); const expected = 'sha256=' + crypto.createHmac('sha256', SECRET) .update(ts + '.').update(req.body).digest('hex'); const a = Buffer.from(expected), b = Buffer.from(sig); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401); const e = JSON.parse(req.body.toString('utf8')); if (e.event === 'code_received') { /* e.activationId, e.code */ } res.sendStatus(200); // верните 2xx в течение 5 с });

Проверка на Python

import hmac, hashlib, time from flask import Flask, request, abort app = Flask(__name__) SECRET = b"whsec_..." # секрет из вашего профиля @app.post("/turonay-webhook") def hook(): raw = request.get_data() # СЫРОЕ тело, а не разобранный JSON ts = request.headers.get("X-Turonay-Timestamp", "") sig = request.headers.get("X-Turonay-Signature", "") if not ts.isdigit() or abs(time.time() - int(ts)) > 300: abort(400) expected = "sha256=" + hmac.new(SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, sig): abort(401) e = request.get_json(force=True) if e["event"] == "code_received": print(e["activationId"], e["code"]) return "", 200 # верните 2xx в течение 5 с

Политика повторов

Ваш сервер должен ответить в течение 5 секунд статусом 2xx. Любой другой статус, тайм-аут или ошибка соединения считаются неудачей. Тело ответа не читается.

Неудачная доставка повторяется до 8 раз; интервалы: 1, 2, 4, 8, 16, 32 и 60 минут (всего около 2 часов). Если все попытки неудачны, событие отбрасывается.

То же событие может прийти повторно (тот же id); делайте обработку идемпотентной по activationId. Порядок событий не гарантируется.

Webhook — удобство, а не единственная гарантия: если вы пропустили событие, состояние всегда можно запросить через getStatus.

Частые вопросы

О чём разработчики спрашивают чаще всего.

Поддержка
API платный?

Отдельной платы или подписки за доступ к API нет. Списывается только стоимость арендованных номеров, а операции без кода не оплачиваются.

Заработает ли моя текущая интеграция, написанная под протокол handler_api?

Формат ответов совместим. Обычно достаточно сменить базовый адрес и API-ключ. Коды сервисов и id стран уточните в прайс-листе.

Ответы в JSON или обычным текстом?

Классические методы (getNumber, getStatus, setStatus, getBalance) возвращают обычный текст для совместимости с handler_api; getNumberV2, getStatusV2, getActiveActivations, getOperators и методы цен/стран возвращают JSON.

Что будет, если пересоздать ключ?

Старый ключ сразу становится недействительным, и все запросы с ним возвращают BAD_KEY. Обновите приложение до пересоздания.

Сколько номеров можно арендовать одновременно?

Число одновременных активаций зависит от вашего баланса и лимита запросов в минуту (по умолчанию 60 запросов в минуту на ключ и IP).

Списываются ли деньги, если код не пришёл?

Нет. Оплата списывается только тогда, когда SMS-код дошёл до вас. Если вы отмените активацию до прихода кода или номер истечёт, ничего не списывается.

Что использовать: опрос кода или webhook?

Они работают вместе. Webhook-уведомление ставится в очередь, как только код записан, и обычно доставляется в течение 1–2 минут; getStatus остаётся альтернативным путём и страховкой на случай пропущенных событий и перезапусков. В большинстве интеграций webhook — основной путь, а опрос — резервный.

Коды сервисов

Короткие коды сервисов, id стран и актуальные цены.

Интеграции

Готовые варианты интеграции и поддерживаемые платформы.

Техподдержка

Напишите нам, если застряли на каком-то шаге интеграции.