Callback результатов
Callback позволяет получать изменения расчета купонов без постоянного опроса API.
Использование callback необязательно. Партнер может получать результаты только через методы чтения купонов либо использовать callback вместе с периодической сверкой.
Подключение callback
Если партнер хочет использовать callback:
- сообщите об этом менеджеру;
- передайте менеджеру необходимые данные для подключения;
- менеджер включит callback и создаст секретную фразу
callback_secret; - сохраните секрет на серверной стороне;
- передавайте URL обработчика в
callback_urlпри создании каждого купона.
Секретная фраза используется для проверки HMAC-подписи. Она не возвращается клиентским API и не должна попадать в браузер, логи или публичный код.
Условия отправки
Callback отправляется, только если одновременно:
- менеджер включил callback для аккаунта;
- для аккаунта настроена секретная фраза;
- при создании конкретного купона передан непустой
callback_url.
Если хотя бы одно условие не выполнено, результат нужно получать запросами API.
URL обработчика
callback_url партнер определяет самостоятельно и передает при создании купона:
{
"callback_url": "https://partner.example.com/api/coupon-result"
}
Правила:
- в production используйте только
https://; - в тестовом окружении допускается
http://; - один купон может иметь только один
callback_url; - разные купоны могут использовать разные URL;
- URL сохраняется вместе с купоном и используется для его последующих обновлений;
- endpoint должен принимать метод
POSTи JSON.
При multi = true URL сохраняется для каждого созданного ординара.
Если callback не используется, поле можно не передавать, передать null, пустую строку или основной домен сайта партнера. Основной домен не является обработчиком callback и должен использоваться только при отключенном callback.
Сетевой доступ и IP allowlist
Endpoint должен быть доступен для входящих запросов Системы расчета купонов SportAPI.
IP-фильтрация на стороне партнера необязательна. Конкретный исходящий IP в публичной документации не фиксируется. Если партнер хочет использовать allowlist, актуальный адрес необходимо получить у менеджера.
Независимо от IP-фильтрации проверка HMAC-подписи обязательна.
HTTP-запрос
Система отправляет:
POST {callback_url} HTTP/1.1
Content-Type: application/json
Accept: application/json
X-Coupon-Signature: sha256=<hex_hmac_sha256>
Подпись вычисляется по точным байтам JSON-тела. Не разбирайте и не сериализуйте JSON повторно до проверки подписи.
Полный алгоритм и примеры кода приведены в разделе «Проверка подписи callback».
Тип события
Поле event всегда содержит:
coupons.settled
Это значение используется для:
- окончательного расчета ординара;
- промежуточного состояния экспресса;
- первого проигрыша экспресса;
- финального состояния экспресса;
- возврата купона на перерасчет;
- нового результата после перерасчета.
Название события не означает, что каждый полученный снимок обязательно является финальным. Всегда проверяйте coupons[].status и статусы ставок.
Пакетная доставка
Один HTTP-запрос может содержать до 100 снимков купонов:
{
"couponCount": 2,
"coupons": [
{},
{}
]
}
couponCount показывает количество элементов в coupons. Обработчик должен перебрать весь массив, а не только первый купон.
В один пакет входят снимки одного аккаунта и одного callback_url.
Пример payload
Пример промежуточного состояния экспресса:
{
"event": "coupons.settled",
"batchId": "d407e986f3a64d9d36a77bf532322ef8",
"clientId": 17,
"couponCount": 1,
"coupons": [
{
"coupon_code": "000000000272",
"realWin": 0,
"calculate_coefficient": 0,
"status": 0,
"calculate_date": 1784897500000,
"events_data": [
{
"uuid": "912",
"status": 1,
"calculate_coefficient": 2,
"calculate_date": 1784897500000,
"calculate_score": "2:1",
"settlement_reason_code": "REMOTE_WIN",
"settlement_reason": "Win",
"timer": 0
},
{
"uuid": "913",
"status": 0,
"calculate_coefficient": null,
"calculate_date": null,
"calculate_score": "",
"settlement_reason_code": null,
"settlement_reason": null,
"timer": 0
}
]
}
]
}
В этом примере первая ставка рассчитана, а вторая еще активна. Поэтому купон сохраняет статус 0, а realWin и calculate_coefficient пока равны 0.
Поля верхнего уровня
| Поле | Тип | Описание |
|---|---|---|
event | string | Тип события. Всегда coupons.settled. |
batchId | string | Идентификатор конкретной версии пакета callback. Используется для идемпотентности. |
clientId | integer | Внутренний ID аккаунта партнера. Имеет информационный характер. |
couponCount | integer | Количество элементов в массиве coupons. |
coupons | array | Расчетные снимки купонов. |
Поля снимка купона
| Поле | Тип | Описание |
|---|---|---|
coupon_code | string | Публичный код купона. |
realWin | number | Фактическая выплата. В промежуточном снимке — 0. |
calculate_coefficient | number | Итоговый расчетный коэффициент. В промежуточном снимке — 0. |
status | integer | Текущий статус купона. |
calculate_date | integer/null | Последняя дата расчета среди рассчитанных ставок, Unix-миллисекунды. |
events_data | array | Все ставки купона, включая еще не рассчитанные. |
Поля ставки
| Поле | Тип | Описание |
|---|---|---|
uuid | string | Стабильный внутренний ID ставки внутри купона. |
status | integer | Текущий статус ставки. |
calculate_coefficient | number/null | Расчетный множитель ставки. |
calculate_date | integer/null | Дата расчета ставки, Unix-миллисекунды. |
calculate_score | string | Счет расчета; для активной ставки может быть пустой строкой. |
settlement_reason_code | string/null | Стабильный машиночитаемый код причины расчета или возврата; до расчета null. |
settlement_reason | string/null | Текст причины расчета или возврата; до расчета null. |
timer | integer | Поле совместимости, сейчас имеет значение 0. |
Используйте settlement_reason_code для аналитики и программного ветвления. settlement_reason может содержать исходный текст причины и предназначен прежде всего для хранения, журналирования или отображения. Набор кодов может расширяться, поэтому неизвестный код не должен приводить к отклонению callback.
Если партнер показывает пользователю причину результата, лучше локализовать собственное сообщение по коду. Например, MATCH_POSTPONED означает перенос матча, MATCH_CANCELLED — отмену, а MARKET_PUSH — возврат по правилам рынка. При неизвестном коде используйте settlement_reason как запасной текст, но не как ключ финансовой логики.
Что отсутствует в callback
Callback содержит расчетный снимок, а не полную карточку купона. В нем нет:
amount;- валюты;
- названий спорта и турнира;
- названий команд;
- названий рынков и исходов;
- исходного указателя ставки.
При этом callback содержит settlement_reason_code и settlement_reason для каждой ставки.
Эти данные следует сохранить после создания купона либо получить через методы чтения купона.
Идентификаторы
Не путайте назначение идентификаторов:
| Поле | Что идентифицирует |
|---|---|
coupon_code | Купон. |
events_data[].uuid | Конкретную ставку внутри купона. |
batchId | Конкретную версию пакета callback. |
В полной модели купона ставка имеет числовое поле events_data[].id. В callback тот же ID передается строкой в uuid.
Рекомендуемые связи в базе партнера:
купон: coupon_code
ставка: coupon_code + uuid
доставка callback: batchId
batchId не является ID купона или ставки.
Несколько callback одного купона
Один coupon_code может штатно прийти несколько раз:
- экспресс обновляется по мере расчета ставок;
- первый проигрыш отправляется сразу;
- после расчета всех ставок проигравшего экспресса приходит финальный снимок;
- перерасчет создает новое состояние.
Каждое новое состояние получает новый batchId. Повторная HTTP-доставка уже созданного пакета сохраняет прежний batchId.
Нельзя считать новое появление coupon_code дубликатом. Обновляйте состояние купона по coupon_code, а повтор одной доставки определяйте по batchId.
Ординар и экспресс
Для ординара callback отправляется после появления результата.
Для экспресса:
- пока экспресс не проиграл, после расчета каждой следующей ставки отправляется новый снимок;
- при первом проигравшем исходе сразу отправляется снимок со статусом купона
4; - следующие промежуточные изменения проигравшего экспресса не отправляются;
- после расчета всех ставок отправляется финальный полный снимок.
Подробный сценарий описан в разделе «Жизненный цикл расчета».
Перерасчет
При возврате купона на перерасчет:
- купон получает статус
15; - соответствующая ставка получает статус
4; - снимок с этим состоянием отправляется сразу;
- после перерасчета отправляется новый снимок с актуальными статусами.
Если предыдущий результат уже был финансово обработан, при первом получении статуса 15 партнер:
- повторно списывает сумму ставки
amount; - ожидает новый финальный результат;
- начисляет новое значение
realWin.
Callback не содержит amount, поэтому сумму нужно сохранить при создании купона либо получить через API чтения.
Финансовая операция выполняется один раз. Повторная доставка с тем же batchId не должна повторно списывать или начислять средства.
Минимальная обработка
Рекомендуемый порядок:
- прочитать исходное тело запроса как байты;
- проверить
X-Coupon-Signature; - начать транзакцию;
- сохранить
batchIdс уникальным ограничением; - если
batchIdуже обработан, не повторять бизнес-операции; - обработать все элементы
coupons; - обновить купоны по
coupon_codeи ставки поuuid; - зафиксировать финансовые операции;
- завершить транзакцию;
- вернуть HTTP
200.
Подробности: «Повторы и идемпотентность».
Успешный ответ
Минимальный успешный ответ:
HTTP/1.1 200 OK
Тело может быть пустым.
Рекомендуемый расширенный ответ:
{
"success": true,
"processed": 1
}
Значение processed должно быть равно couponCount.
Возвращайте HTTP 200 только после успешной фиксации всех элементов пакета.
Ответы 201, 202 и 204 не считаются успешным подтверждением доставки: текущий контракт требует именно HTTP 200.
Отрицательное или частичное подтверждение
Даже при HTTP 200 тело может сообщить об ошибке:
success: false— пакет не обработан;processed < couponCount— обработана только часть пакета.
Такое подтверждение считается финальной ошибкой и автоматически не повторяется. Поэтому не возвращайте success: false или частичный processed в расчете на повторную доставку.
Временные ошибки должны возвращать один из предусмотренных HTTP-кодов. Расписание и условия повторов описаны в разделе «Повторы и идемпотентность».
Следующий раздел: «Проверка подписи callback».