Перед началом
Три вещи и пять минут.
- Учётная запись и баланс. Создайте ключ доступа и пополните баланс криптовалютой. Заказ оплачивается с баланса, поэтому на нём должны быть средства.
- API-ключ с областью действия
admin. Заказ номеров расходует деньги, чего ключиreadиwriteделать не могут. Создайте его в разделе API-ключи; ключ показывается один раз. - Доступный нам конечный адрес HTTPS. Обычный HTTP отклоняется. Для локальной разработки подойдёт любой инструмент туннелирования.
Не храните ключ в репозитории. Ключ admin позволяет заказывать номера, пока баланс не опустеет. Используйте переменную окружения, хранилище секретов — что угодно, кроме файла в репозитории. Если ключ утёк, прежде всего отзовите его в панели.
Шаг 1 — заказать линию
Потребуется один запрос. Он списывает средства с баланса и завершается после подтверждения выделения номера; линия начинает принимать сообщения примерно через минуту.
curl -X POST https://api.incognum.com/v1/numbers \
-H "Authorization: Bearer $NKP_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"country":"de","type":"mobile","period":1,"label":"signup-bot"}'
# 201 Created
{
"id": "num_8f2c1a94",
"e164": "+4915735550088",
"status": "active",
"renews_at": "2026-10-21T09:14:02Z",
"charged": { "monthly": 12.90, "activation": 10.00, "total": 22.90 }
}Всегда передавайте Idempotency-Key. На вашей стороне тайм-аут неотличим от сбоя. С этим заголовком повторный запрос вернёт сохранённый ответ, а не закажет второй номер; ключ хранится 24 часа.
Шаг 2 — зарегистрировать вебхук
Зарегистрируйте каждый конечный адрес один раз и прекратите опрос. Секрет возвращается в этом ответе и больше никогда не показывается, поэтому сохраните его сразу.
curl -X POST https://api.incognum.com/v1/webhooks \
-H "Authorization: Bearer $NKP_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.org/hooks/nkp","events":["message.received"]}'
# 201 Created
{
"id": "whk_77c1e0",
"url": "https://example.org/hooks/nkp",
"events": ["message.received"],
"secret": "whsec_2b91f4c7a0d3…"
}Подписывайтесь только на те события, которые обрабатываете. Для этой задачи полезны следующие:
message.received— получено SMS, код уже извлечён.number.active— заказанная линия активирована.number.grace— продление не удалось из-за недостатка средств. Настройте оповещение: это предупреждение за 3 дней до исчезновения линии.topup.credited— пополнение подтверждено.
Шаг 3 — проверить подпись
Конечный адрес, принимающий любые отправленные ему данные, — не вебхук, а публичный API записи. Проверяйте подпись, прежде чем доверять телу запроса.
Подпись представляет собой HMAC-SHA256 от timestamp + "." + raw_body, где ключом служит секрет конечного адреса. Результат кодируется в шестнадцатеричном виде и передаётся в заголовке X-NKP-Signature как v1=….
import hmac, hashlib, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["NKP_WEBHOOK_SECRET"].encode()
seen = set() # в рабочей среде: Redis с истечением срока хранения
@app.post("/hooks/nkp")
def hook():
raw = request.get_data() # ИСХОДНОЕ тело без повторной сериализации
ts = request.headers.get("X-NKP-Timestamp", "")
sig = request.headers.get("X-NKP-Signature", "").split("=", 1)[-1]
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(400) # слишком старый запрос: повтор
mine = hmac.new(SECRET, f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(mine, sig):
abort(401)
delivery = request.headers["X-NKP-Delivery"]
if delivery in seen:
return "", 200 # уже обработано, но всё равно подтверждаем
seen.add(delivery)
event = request.get_json()
if event["event"] == "message.received":
enqueue(event["data"]) # обрабатываем ПОСЛЕ ответа
return "", 200В этом обработчике важны три вещи; если пропустить любую из них, можно получить реальный сбой:
- Подписывайте исходное тело. Разбор и повторная сериализация JSON меняют байты и делают HMAC недействительным.
- Сравнивайте за постоянное время. Оператор
==при сравнении подписей допускает утечку по времени выполнения. - Отклоняйте старые метки времени. Без пятиминутного окна перехваченный запрос можно воспроизводить бесконечно.
Что вы получите
POST /hooks/nkp HTTP/1.1
X-NKP-Event: message.received
X-NKP-Delivery: evt_6f0b28d4
X-NKP-Timestamp: 1789033541
X-NKP-Signature: v1=6b3a1f8e7c0d94aa5e2b…
Content-Type: application/json
{
"event": "message.received",
"created_at": "2026-09-21T09:45:41Z",
"data": {
"id": "msg_5b1f77c0",
"number": "num_8f2c1a94",
"e164": "+4915735550088",
"from": "Telegram",
"body": "Ваш код входа — 72194. Никому его не сообщайте.",
"code": "72194",
"received_at": "2026-09-21T09:45:41Z"
}
}Поле code извлекается на сервере по тем же правилам, что используются в панели: группы от четырёх до восьми цифр, пробелы и дефисы удаляются, собственный номер отправителя игнорируется. Если ничего похожего на код нет, значение будет null, а поле body останется доступно для самостоятельного разбора.
Повторные попытки, порядок и другие подводные камни
- Доставка выполняется как минимум один раз. Удаляйте дубликаты по
X-NKP-Delivery. Один ID никогда не обозначает два разных события. - Порядок не гарантируется. Два сообщения с разницей в секунду могут прийти в любой последовательности. Если порядок важен, сортируйте по
created_at. - На ответ даётся десять секунд. Более медленный обработчик считается неудачным, и событие доставляется повторно. Сначала подтвердите приём, затем выполняйте работу.
- Восемь попыток примерно за сутки с интервалами 10 с, 1 мин, 5 мин, 30 мин, 2 ч, 6 ч, 12 ч и 24 ч.
- Три дня непрерывных сбоев отключают конечный адрес, о чём сообщит панель. Ничего не теряется: сообщения по-прежнему доступны через API.
Оставьте резервный опрос. Вебхуки обеспечивают скорость, но не являются источником истины. Недорогой запрос GET /v1/messages?since=… раз в несколько минут получит всё, что конечный адрес пропустил во время простоя, и ничего не стоит, когда получать нечего.
Шаг 4 — поддерживать работу
Автоматизация, которая заказывает номера и никогда их не освобождает, незаметно опустошает баланс. Заведите две привычки:
- Освобождайте ненужные линии. Запрос
DELETE /v1/numbers/{id}выполняется немедленно и прекращает продление. Вместе с линией удаляются сообщения, поэтому сначала прочитайте их. - Следите за
number.grace. Это единственное предупреждение перед автоматическим освобождением линии, и оно приходит за 3 дней.
# освободить все линии с меткой "signup-bot", неактивные в течение недели
curl -s "https://api.incognum.com/v1/numbers?label=signup-bot" \
-H "Authorization: Bearer $NKP_KEY" |
jq -r '.data[] | select(.last_message_at < (now - 604800 | todate)) | .id' |
while read -r id; do
curl -s -X DELETE "https://api.incognum.com/v1/numbers/$id" \
-H "Authorization: Bearer $NKP_KEY"
doneОграничения составляют 120 запросов чтения и 30 запросов записи в минуту на ключ; для заказов действует отдельный предел. Каждый ответ содержит X-RateLimit-Remaining. Получив 429, соблюдайте Retry-After, а не отправляйте запросы без остановки. Все значения приведены в справочнике API.
Частые вопросы
Обязательно ли использовать вебхуки или достаточно опроса?
В небольшом масштабе опрос вполне подходит, и его проще отлаживать. Вебхуки приходят в течение секунды, а не через очередной интервал опроса, и ничего не расходуют, когда событий нет. Большинство в итоге использует оба способа: вебхуки для скорости и редкий опрос в качестве страховки.
Почему не проходит проверка подписи?
Почти всегда причина в том, что тело разобрали и повторно сериализовали перед хешированием. Подписывайте исходные байты точно в полученном виде. Вторая по частоте ошибка — хеширование только тела вместо метки времени, точки и затем тела.
Можно ли назначить отдельный вебхук каждому номеру?
Да. URL вебхука для отдельной линии, заданный запросом PATCH к номеру, имеет приоритет над общим конечным адресом учётной записи. Это удобный способ направлять разные автоматизации в разные сервисы.
Что произойдёт, если мой сервер не работает?
Мы сделаем восемь повторных попыток примерно за сутки. После трёх дней непрерывных сбоев конечный адрес отключается, и панель сообщает об этом. Сообщения в любом случае не теряются: все они остаются доступными через API.
Есть ли официальный SDK?
Нет, и это сознательное решение. Интерфейс настолько мал, что оболочка устаревала бы быстрее самих HTTP-запросов. В справочнике API приведён полный клиент для Node и Python — примерно по пятнадцать строк на каждый.
Материал подготовила команда, управляющая этим сервисом, и обновила 21.05.2026.
