Разработчикам
Справочник API
Одна конечная точка для отправки, одна для чтения статуса, вебхуки с HMAC-подписью для уведомлений. Ключи выдаются в момент создания аккаунта — одобрять нечего и писать по почте некому.
Начало работы
Между новым аккаунтом и доставленным сообщением стоят всего три вещи: токен, средства на балансе и ключ. Ни одна из них не требует участия человека.
- Создайте аккаунт — ваш браузер генерирует токен, и этот токен и есть аккаунт.
- Пополните его. Минимум $40, любой из монет на страница платежей.
- Создайте API-ключ в личном кабинете. Он показывается один раз; мы храним только его хеш, точно так же, как токен.
Ключ с пустым балансом не может отправлять
Создание ключа намеренно заблокировано, пока аккаунт не пополнен. Неиспользуемый ключ, лежащий без дела, — это риск без всякой пользы, поэтому личный кабинет не выдаст его, пока не появится что тратить.
Аутентификация
Токен доступа при каждом запросе. Ключ идентифицирует аккаунт; нет ни второго фактора, ни подписи запроса, ни списка разрешённых IP-адресов — ключ является единственным учётным данным, так к нему и относитесь.
Authorization: Bearer ws_live_9f2c…
| Транспорт | Только HTTPS. Запрос по обычному HTTP отклоняется, а не перенаправляется — перенаправление уже раскрыло бы ключ. |
|---|---|
| Область | Один ключ расходует баланс одного аккаунта. Создавайте отдельный ключ для каждой системы, которая отправляет сообщения, — тогда утечку можно отозвать точечно. |
| Отзыв | Мгновенно. Отозванный ключ возвращает 401 при следующем запросе; уже принятые рассылки в процессе отправки всё равно завершатся. |
| Восстановление | Никаких. Мы храним только хеш. Утерянный ключ заменяется, а не восстанавливается. |
Отправка сообщений
POST/v1/messages
Один вызов, один или несколько получателей, разные направления в одном запросе. Каждый получатель тарифицируется по ставке своего направления и списывается за сегмент. Вызов принимается или отклоняется целиком; частичного списания не бывает.
Тело запроса
| Поле | Тип | Заметки |
|---|---|---|
to | string[] | Обязательно. E.164, до 5 000 за один вызов. Номера, не соответствующие ни одному тарифицируемому направлению, отклоняются до списания средств. |
text | string | Обязательно. UTF-8. Количество сегментов считается по GSM 03.38 — см. сегменты. |
from | string | Буквенно-цифровое имя отправителя, до 11 символов. Передаётся там, где это позволяет маршрут; см. имена отправителя. |
vars | object[] | Необязательные значения для подстановки, по одному объекту на получателя, вставляются в плейсхолдеры {{name}} до подсчёта сегментов. |
callback_url | string | Необязательное переопределение вебхука аккаунта для отдельного пакета. |
reference | string | Необязательно. Возвращается в каждом отчёте, чтобы вы могли сопоставить его со своими записями. |
Пример
# one call, many recipients, mixed destinations
curl -X POST https://api.worldsms.io/v1/messages \
-H "Authorization: Bearer $WORLDSMS_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "ACME",
"to": ["+447700900123", "+33612345678"],
"text": "Your code is 480913"
}'
{
"batch_id": "b_7Kq2xR9wLm",
"accepted": 2,
"rejected": 0,
"segments": 1,
"cost_usd": 0.0353
}
0,0137 $ на направление Великобритания плюс 0,0216 $ на направление Франция — тарифы применяются по каждому направлению отдельно, а не как усреднённое значение.
Группировка
Пакет принимается немедленно; количество сообщений в минуту на аккаунт — 200, поэтому при количестве получателей 50 000 время составит около 4 ч 10 мин.
Статус сообщения
GET/v1/messages/{batch_id}
Опрос поддерживается и ограничен по частоте; предполагаемый путь — вебхуки. Ответ сводит пакет воедино и перечисляет статус по каждому получателю.
{
"batch_id": "b_7Kq2xR9wLm",
"status": "done",
"sending": 0,
"delivered": 1,
"undelivered": 1,
"refund_usd": 0.0216
}
refund_usd рассчитывается один раз, когда рассылка завершается. К моменту, когда вы это увидите, средства уже вернулись на баланс.
Баланс
GET/v1/balance
Проверяйте перед крупной рассылкой. Внутри баланс хранится в целых микродолларах — тарифы указываются с точностью до четырёх знаков, а числу с плавающей точкой не место в журнале операций — а здесь возвращается округлённым до шести знаков.
{ "balance_usd": 412.905400, "currency": "USD" }
Отчёты о доставке
Отчёт формируется по каждому получателю по мере поступления данных от оператора. Это единственные четыре состояния; нет никакой скрытой категории «неизвестно», которая на деле означала бы «мы его потеряли».
| Статус | Значение | Списано |
|---|---|---|
queued | Принято и ожидает своей очереди в пакете. | Да |
sent | Передано оператору, подтверждения ещё нет. | Да |
delivered | Телефон подтвердил получение. | Да |
undelivered | Номер недействителен, заблокирован, либо отправитель отклонён. | Возвращено |
Номера, которые не относятся ни к одному тарифицируемому направлению, отклоняются при отправке и вообще не попадают в этот жизненный цикл — они не списываются с последующим возвратом, они просто не списываются.
Вебхуки
Задайте адрес в личном кабинете или для каждого пакета через callback_url. Мы отправляем JSON POST-запросом и ожидаем 2xx в течение 5 секунд. Если код ответа не 2xx, запрос повторяется шесть раз с экспоненциальной задержкой в течение примерно часа, а затем отбрасывается.
Полезная нагрузка
{
"event": "message.delivered",
"batch_id": "b_7Kq2xR9wLm",
"to": "+447700900123",
"destination": "gb",
"segments": 1,
"cost_usd": 0.0137,
"reference": "order-4471",
"ts": 1785312041
}
Проверка подписи
Каждая доставка несёт X-WorldSMS-Signature и X-WorldSMS-Timestamp. Подпись — HMAC-SHA256(timestamp + "." + body) с использованием вашего секрета вебхука. Сравнивайте за постоянное время и отклоняйте метку времени старше пяти минут, чтобы перехваченную доставку нельзя было воспроизвести позже.
// $secret is the webhook secret from the dashboard
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_WORLDSMS_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_WORLDSMS_SIGNATURE'] ?? '';
// A stale timestamp is a replay, however well it is signed.
if (abs(time() - (int) $ts) > 300) { http_response_code(408); exit; }
$mine = hash_hmac('sha256', $ts . '.' . $raw, $secret);
if (!hash_equals($mine, $sig)) { http_response_code(401); exit; }
http_response_code(204);
Быть идемпотентным
Повторная попытка после тайм-аута вашего эндпоинта выглядит так же, как первая доставка. Используйте ключ batch_id + to + event и обрабатывайте повтор как отсутствие действия.
Ошибки
Каждая ошибка возвращает тело JSON со стабильным идентификатором error. Сверяйтесь по идентификатору, а не по тексту описания — текст может меняться в лучшую сторону.
| HTTP | Слаг | Что делать |
|---|---|---|
| 400 | invalid_body | Некорректный JSON или отсутствует обязательное поле. |
| 401 | key_rejected | Ключ отсутствует, отозван или введён с опечаткой. Повтор невозможен. |
| 402 | insufficient_credit | Пакет стоит больше, чем есть на балансе. Ничего не отправлено; пополните счёт и отправьте заново. |
| 422 | no_valid_recipients | Ни один номер не удалось привязать к тарифицируемому направлению. |
| 422 | sender_id_invalid | from длиннее 11 символов или содержит строку только из цифр, которая будет прочитана как число. |
| 429 | rate_limited | Снизьте частоту запросов. Retry-After содержит число секунд. |
| 503 | route_unavailable | Направление временно недоступно для маршрутизации. Можно повторить. |
{
"error": "insufficient_credit",
"message": "Batch costs $84.20, balance is $12.05.",
"required_usd": 84.20,
"balance_usd": 12.05
}
Лимиты
| Запросы | 60 в минуту на ключ, всплеск 20. Сверх этого — 429 с Retry-After. |
|---|---|
| Получателей за вызов | 5 000. Разбейте более длинные списки на несколько вызовов либо загрузите файл в личном кабинете. |
| Размер сообщения | 2 MB. |
| Пропускная способность | 200 сообщений в минуту на аккаунт, независимо от того, сколько ключей отправляет. |
| Ключи | Без ограничений. Предполагаемая схема — один на систему отправки. |
Лимиты применяются к вызовам API, а не к сообщениям: один вызов с 5000 получателей расходует один запрос из лимита.
Ключи выдаются мгновенно. Этап одобрения отсутствует.
Пополните аккаунт — и личный кабинет создаст его сразу же.