Заголовок Idempotency-Key гарантирует, что повтор запроса на покупку не спишет деньги дважды.
Зачем это нужно
Сеть ненадёжна: запрос мог дойти и купить номер, а ответ — потеряться. Если просто отправить покупку заново, вы получите второй номер. С ключом идемпотентности повтор вернёт тот же заказ.
Правила
Заголовок обязателен для POST /v1/numbers. Без него — 400 missing_idempotency_key.
Ключ — строка длиной от 1 до 128 символов. Удобнее всего UUID.
Один ключ — одна покупка. Для нового номера создавайте новый ключ.
Повтор с тем же ключом и теми же service и country возвращает сохранённый результат.
Когда повторять запрос
Ответ
Что делать
409idempotency_key_processing
Покупка ещё идёт. Повторите через несколько секунд с тем же ключом.
504upstream_timeout
Сервис не успел ответить. Повтор с тем же ключом безопасен.
Сетевая ошибка или таймаут клиента
Повторите с тем же ключом.
409idempotency_key_reused_with_different_payload
Ключ уже использован для другой пары сервис/страна. Создайте новый ключ.
Изменение цены
Если во время покупки цена у провайдера выросла, заказ всё равно создаётся по итоговой цене, а в ответе появляются price_changed: true и previous_price — цена по витрине до пересчёта.
Номер выдан, SMS ещё нет. Можно ждать или отменить.
sms_received
Пришла хотя бы одна SMS, коды доступны в sms_codes.
finished
Работа с заказом завершена.
canceled
Заказ отменён, номер освобождён.
refunded
Средства за заказ возвращены на баланс.
Методы по шагам
Шаг
Метод
Зачем
Каталог
GET/v1/services
Сервисы с наличием и минимальной ценой. sort=popularity или name.
Страны
GET/v1/services/{code}/countries
Страны для сервиса и лучшая цена. sort=price или name.
Предложения
GET/v1/services/{code}/offers?country=0
Все цены и остатки для сервиса в стране.
Покупка
POST/v1/numbers
Создать заказ. Нужен Idempotency-Key.
Статус
GET/v1/numbers/{order_id}
Заказ целиком, включая sms_codes.
Коды
GET/v1/orders/{order_id}/sms
Только статус и список кодов.
Активные
GET/v1/numbers
Заказы в pending и sms_received, до 200 штук.
Завершение
POST/v1/orders/{order_id}/finish
Закрыть заказ после получения SMS.
Отмена
POST/v1/numbers/{order_id}/cancel
Отменить заказ без SMS и вернуть средства.
История
GET/v1/orders/history
Прошлые активации с фильтром по периоду.
Отмена заказа
Отменить можно только свой заказ в статусе pending, по которому ещё не пришла SMS.
Сразу после покупки отмена недоступна несколько секунд, у части провайдеров — до 2 минут. Сколько осталось, подскажет details.seconds_left в ответе 409 too_early.
После получения кода отмена недоступна (409 already_has_code) — завершите заказ.
Заказ в другом статусе вернёт 409 bad_status.
Завершение
POST /v1/orders/{order_id}/finish отвечает 204 без тела. До получения SMS или в неподходящем статусе — 409 cannot_complete.
История активаций
Фильтруйте по периоду через start и end (Unix time). Для больших историй используйте курсор: передайте next_after_id из ответа в параметр after_id, пока has_more не станет false. История сохраняет стабильный порядок по времени и ID заказа, размер страницы — size, до 100.
POST /v1/numbers/{order_id}/cancel
curl -X POST https://api.hush-sms.com/v1/numbers/66f2b8a8e7ad0a0012c34567/cancel \
-H "Authorization: Bearer $HUSH_API_KEY"
{
"error": {
"code": "too_early",
"message": "Cancel not allowed until 120s after order creation for this provider",
"details": { "seconds_left": 45 }
}
}
Полный пример
Скрипт на Python покупает номер, ждёт код до 5 минут и завершает заказ, а если SMS не пришла — отменяет его.
Покупка повторяется только при 504 и idempotency_key_processing — и всегда с тем же ключом.
Остальные ошибки выбрасываются исключением: их нужно обработать по таблице ошибок.
Нужны Python 3.10+ и библиотека requests.
buy_number.py
import os
import time
import uuid
import requests
API = "https://api.hush-sms.com"
HEADERS = {"Authorization": f"Bearer {os.environ['HUSH_API_KEY']}"}
def error_code(resp):
try:
return resp.json()["error"]["code"]
except (ValueError, KeyError, TypeError):
return None
def buy_number(service: str, country: int) -> dict:
key = str(uuid.uuid4()) # один ключ на одну покупку
for attempt in range(3):
resp = requests.post(
f"{API}/v1/numbers",
headers={**HEADERS, "Idempotency-Key": key},
json={"service": service, "country": country},
timeout=30,
)
retry = resp.status_code == 504 or error_code(resp) == "idempotency_key_processing"
if retry and attempt < 2:
time.sleep(3)
continue
resp.raise_for_status()
return resp.json()["order"]
def wait_for_code(order_id: str, timeout_sec: int = 300) -> str | None:
deadline = time.monotonic() + timeout_sec
while time.monotonic() < deadline:
resp = requests.get(f"{API}/v1/numbers/{order_id}", headers=HEADERS, timeout=15)
resp.raise_for_status()
order = resp.json()["order"]
if order.get("sms_codes"):
return order["sms_codes"][-1]
if order["status"] != "pending":
return None
time.sleep(4)
return None
order = buy_number("tg", 0)
print("Номер:", order["phone_number"])
code = wait_for_code(order["id"])
if code:
print("Код:", code)
requests.post(f"{API}/v1/orders/{order['id']}/finish", headers=HEADERS, timeout=15)
else:
requests.post(f"{API}/v1/numbers/{order['id']}/cancel", headers=HEADERS, timeout=15)
Руководства
Webhooks
Получайте входящие SMS на свой сервер сразу, без опроса статуса заказа.
Настройка
В @HushNumBot откройте Информация → API → Webhooks и нажмите Установить webhook.
Требования к адресу
Только https://, до 500 символов.
Публичный хост: локальные и приватные адреса запрещены.
Без логина и пароля в адресе.
Один адрес на аккаунт.
Тестовая отправка
Кнопка Тестовый webhook отправляет пример события не чаще раза в 60 секунд. У тестового события "test": true и "activationId": "test-webhook".
Код из SMS: первое число из 4–8 цифр. Если числа нет — весь текст.
test
boolean
true для тестовой отправки из бота.
Ответ и повторы
Ответьте любым кодом 2xx в течение 12 секунд — событие будет считаться доставленным. Тяжёлую обработку выполняйте после ответа, в фоне. Редиректы не выполняются.
Когда мы повторяем доставку
Сетевая ошибка или таймаут, ответы 408, 425, 429 и любые 5xx.
До 8 попыток с растущей паузой: 5 с, 10 с, 20 с, 40 с и далее.
Остальные коды 4xx не повторяются — событие считается отклонённым.
Одно событие может прийти повторно, если ваш сервер обработал его, но не успел ответить. Обрабатывайте события идемпотентно — например, по паре activationId + code.
Webhook дополняет, а не заменяет API: в GET /v1/numbers/{order_id} коды доступны всегда.
webhook.py · Flask
from flask import Flask, request
app = Flask(__name__)
seen = set() # в продакшене — таблица в базе данных
@app.post("/hooks/hush")
def hush_webhook():
if request.headers.get("X-Hush-Event") != "sms.received":
return "", 204
event = request.get_json(force=True)
if event.get("test"):
return "", 204
key = (event["activationId"], event.get("code"))
if key not in seen:
seen.add(key)
save_code(event["activationId"], event["code"], event["text"])
return "", 204
Руководства
Ошибки и лимиты
Все ошибки возвращаются в одном формате. Ориентируйтесь на поле code — текст message может меняться.
Формат ошибки
code — стабильный машинный код, по нему стоит строить логику.
message — описание для логов.
details — дополнительные данные, если есть. Например, seconds_left для too_early.