Skip to content
Translated page. The English version is the source of truth.

POST /apiv2/reports/webhooks

Зарегистрируйте URL, и NETTS вызовет его, когда отчет будет готов, избавляя вас от необходимости опрашивать статус вручную.

Эти эндпоинты отделены от вебхуков заказов. Регистрация там не подписывает вас на уведомления об отчетах, и наоборот. Формат передачи данных — подпись, заголовки, логика повторов — абсолютно одинаков, поэтому обработчик, написанный для одного из них, подойдет и для другого.

Базовый URL эндпоинта

https://netts.io/apiv2/reports/webhooks

Заголовки запроса

ЗаголовокОбязательныйОписание
X-API-KEYдаAPI-ключ из панели управления
X-Real-IPдаАдрес из белого списка ключа

Основной и резервный

До двух эндпоинтов на аккаунт. primary получает все события. backup используется только после того, как исчерпаны все попытки доставки на основной — и он подписывается своим собственным секретным ключом, а не ключом основного.

Регистрация

bash
curl -s -X POST 'https://netts.io/apiv2/reports/webhooks' \
  -H 'X-API-KEY: your-api-key' \
  -H 'X-Real-IP: 203.0.113.10' \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://example.com/netts/reports", "role": "primary"}'
json
{
  "status": "success",
  "code": 10000,
  "data": {
    "id": 1,
    "url": "https://example.com/netts/reports",
    "role": "primary",
    "is_active": true,
    "created_at": "2026-09-06 17:05:12+00:00",
    "updated_at": "2026-09-06 17:05:12+00:00",
    "secret": "whsec_<64 hex characters>"
  }
}

Секретный ключ отображается только один раз, здесь. Он больше никогда не возвращается — ни в списке, ни в эндпоинте чтения. Сохраните его при получении. Если он утерян, сгенерируйте новый:

bash
curl -s -X POST 'https://netts.io/apiv2/reports/webhooks/1/rotate-secret' \
  -H 'X-API-KEY: your-api-key' -H 'X-Real-IP: 203.0.113.10'

Ротация вступает в силу немедленно, и старый секрет перестает проходить проверку, поэтому сначала разверните новое значение, если вы не можете допустить перерыва в обработке.

Управление

МетодПутьДействие
GET/apiv2/reports/webhooksсписок ваших эндпоинтов, без секретных ключей
GET/apiv2/reports/webhooks/{id}получить один эндпоинт
PATCH/apiv2/reports/webhooks/{id}изменить url или приостановить с помощью is_active: false
DELETE/apiv2/reports/webhooks/{id}удалить эндпоинт

URL должен быть публичным адресом HTTPS. Loopback-, приватные и link-local адреса отклоняются, как и учетные данные внутри URL. Любой отклоненный запрос возвращает статус 422 с указанием причины. Проверка запускается снова непосредственно перед каждой доставкой, поэтому эндпоинт, который позже начнет разрешаться в приватный адрес, перестанет получать уведомления.

Что мы отправляем

json
{
  "event": "report.ready",
  "delivery_id": 4,
  "order_id": "REPxxxxxxxxxxxx",
  "order_type": "statement",
  "client_request_id": "stmt-2026-09-usdt",
  "status": "done",
  "format": "csv",
  "download_url": "/apiv2/reports/REPxxxxxxxxxxxx/download",
  "expires_at": "2026-10-06 15:48:04+00:00",
  "artifact": { "sha256": "…", "size_bytes": 696 },
  "confirmed_at": "2026-09-06T15:48:04Z"
}
ПолеОписание
eventreport.ready — ключ маршрутизации для вашего обработчика
delivery_idКлюч дедупликации. Также отправляется в заголовке X-Netts-Delivery
order_idНомер заказа, полученный вами при постановке отчета в очередь
order_typestatement или balance_at_date
download_urlПуть для скачивания файла относительно https://netts.io
artifact.sha256Контрольная сумма для проверки скачанного файла
confirmed_atUTC

Все временные метки указаны в формате UTC.

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

X-Netts-Signature: sha256=<hex>
X-Netts-Timestamp: <unix seconds>
X-Netts-Delivery:  <delivery_id>

Подпись представляет собой HMAC-SHA256 от "<timestamp>." + raw body, вычисленный с использованием секретного ключа того эндпоинта, который получил запрос. Сравнивайте за константное время и отклоняйте любые запросы, временная метка которых выходит за пределы окна в ±5 минут.

python
import hmac, hashlib, time

def verify(raw_body: bytes, sig_header: str, ts_header: str, secret: str) -> bool:
    if abs(time.time() - int(ts_header)) > 300:      # anti-replay
        return False
    signed = f"{ts_header}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig_header)

Выполняйте подписание с помощью секрета того URL, на который поступил запрос: у основного и резервного эндпоинтов секреты различаются.

Доставка гарантируется по модели at-least-once

Потеря ответа приводит к повторной отправке, поэтому одно и то же событие может прийти дважды.

  1. Дедуплицируйте по delivery_id. Повторный запрос должен быть безопасной холостой операцией (no-op) на вашей стороне.
  2. Проверяйте подпись до выполнения действий, а не после.
  3. Отвечайте кодом 2xx только после сохранения события. Любой другой ответ или тайм-аут считаются ошибкой и приводят к повторным попыткам.

Повторные попытки на один эндпоинт отправляются через 1 минуту, 5 минут, 15 минут, 1 час, 6 часов и 24 часа — всего шесть попыток, охватывающих чуть более 31 часа. Когда они исчерпаны, и вы зарегистрировали backup, доставка переключается на него, и расписание начинается заново с собственным секретом резервного эндпоинта. Значение delivery_id остается неизменным на всем протяжении, поэтому событие, завершившееся ошибкой на основном эндпоинте и успешно доставленное на резервный, остается одним и тем же событием.

Перенаправления (редиректы) не поддерживаются.

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

10 запросов в секунду на эндпоинт, суммарно для всех клиентов.

Ошибки

Регистрация возвращает 201, удаление — 204 без тела ответа, все остальные запросы — 200.

HTTPЗначение
401ключ отсутствует или недействителен, либо IP-адрес источника не добавлен в белый список
404такой эндпоинт отсутствует в вашем аккаунте
409запрошенная роль уже занята — role primary is already taken
422URL был отклонен, либо тело PATCH не содержит изменений
429превышено ограничение частоты запросов

Отклоненный URL возвращается со статусом 422 и явным описанием причины, чтобы вы могли показать ее тому, кто его ввел:

json
{"status": "error", "code": -4, "msg": "url: Value error, invalid webhook_url: resolved address 127.0.0.1 is not public"}

Формулировки ошибок: only https:// URLs are allowed, credentials in URL are not allowed и resolved address <ip> is not public. Последняя проверяется во время регистрации и повторно непосредственно перед каждой доставкой, поэтому имя хоста, которое позже станет указывать на приватный адрес, перестанет получать уведомления.

Связанные разделы