Партнёрский API организации (API-ключи)
HTTP-API, по которому сторонний партнёр владельца клуба читает данные его организации: касса, система лояльности, сайт с онлайн-записью, собственная витрина сети. Ключ выдаёт владелец в панели (раздел API-ключи), он же решает, что ключу доступно и до какого момента он работает.
Это не протокол игровой станции (он работает по WebSocket) и не API мобильного приложения клиента — это отдельные контракты. Здесь нет клиентских сессий и авторизации человека: запрос приходит от программы партнёра и опознаётся ключом.
Postman. Для импорта готовы два файла:
| Файл | Что это |
|---|---|
| spike_api_postman_collection.json | Коллекция: 51 запрос в 8 папках — все действия плюс проверки отказов, с готовыми тестами |
| spike_api_postman_environment.json | Окружение: base_url и api_key (тип *secret*), остальные переменные заполняются сами |
Импортируйте оба (*Import* → перетащить файлы), выберите окружение в правом верхнем углу и впишите в него api_key. Ключ держите в окружении, а не в коллекции: коллекцию передают и экспортируют, а окружение остаётся у вас.
Дальше можно запускать Runner'ом целиком — списки запоминают id первого элемента, поэтому gs.get, product.get, session.get, order.get и chat.messages работают без ручного ввода. Папки 1–7 ждут ответа 200, то есть рассчитаны на ключ с полным доступом; ключ с урезанными правами законно ответит 403 forbidden_action на недоступные действия — что именно ему доступно, говорит meta.actions.
---
1. Где взять ключ
Ключ выдаёт владелец клуба в своей панели: раздел API-ключи → «Создать». Там же он выбирает, что ключу доступно и до какого момента он работает, поэтому просить ключ нужно вместе с перечнем нужных вам действий — владелец отметит именно их.
Что сказать владельцу:
- какие данные нужны — по действиям из §4 (например
gs.status,product.list); в карточке ключа он отмечает права с теми же названиями, что подписаны рядом с действием; - на какой срок — ключ можно выдать на день, неделю, месяц, год, до конкретной даты или бессрочно;
- нужен ли журнал обращений — если интеграцию ещё отлаживают, владелец включает галочку и видит ваши запросы (см. §8).
Готовый ключ он передаёт вам как пароль. В списке ключей он виден маской (первые и последние четыре символа), целиком — в карточке, откуда его и копируют.
Подробнее со стороны владельца — в руководстве пользователя SpikePC, раздел API-ключи.
Ключ не привязан к клубу: он действует на всю организацию, а конкретный клуб вы указываете параметром club_id.
---
2. Транспорт
POST https://spikepc.com/dev/spike_api/
X-Api-Key: 7b2d4f9a1c3e5d7f9b0a2c4e6f8a0b1d
Content-Type: application/json
{ "action": "gs.list", "v": 1, "data": { "club_id": 3, "limit": 50, "offset": 0 } }
Адрес указывайте со слэшем на конце (/spike_api/). Без слэша веб-сервер отвечает редиректом на адрес со слэшем, а тело POST-запроса при редиректе теряется — запрос придёт пустым и получит 400 bad_request.
Тот же обработчик доступен и по внутреннему адресу ?loc=spike_user_api_request — он остаётся рабочим, но в интеграциях используйте /spike_api/.
| Правило | Почему так |
|---|---|
Только POST, тело — один JSON-объект | Параметры в query до обработчика не доходят: панель пропускает из URL лишь белый список (id, loc, page, token, hash) |
Ключ — заголовок X-Api-Key | Проверено: фронтовый прокси его пропускает. Authorization: Bearer до PHP не доходит вовсе, использовать его нельзя |
Фолбэк — поле api_key в теле | Для клиентов, которые не умеют заголовки. В журнал это поле не пишется |
| Ключ не передаётся в URL | Адреса оседают в логах прокси и веб-сервера |
Прикладные параметры — внутри data | Системные поля (action, v, api_key) лежат рядом, не смешиваясь с прикладными |
org_id в запросе не принимается | Организация берётся из ключа. Иначе партнёр одного клуба читал бы чужие данные |
club_id принимается | Но сверяется с клубами организации ключа → иначе club_foreign (403) |
v — версия контракта, необязательна (сейчас 1).
---
3. Ответ
Тот же конверт, что у станции и мобильного приложения; code дублируется в HTTP-статусе:
{
"code": 200,
"error_key": "",
"error_text": "",
"action": "gs.list",
"data": { "items": [ … ], "total": 38, "limit": 50, "offset": 0 }
}
error_key— стабильный машинный код, на него и опирайтесь.error_text— пояснение по-английски, его формулировка может меняться.dataвсегда объект (при ошибке —{}): в объект можно добавить поле, не ломая разбор. Списки —items+total+limit/offset, одна запись —item.action— эхо запрошенного действия, удобно при асинхронной очереди на стороне партнёра.
Коды и ключи ошибок
| HTTP | error_key | Когда |
|---|---|---|
| 400 | bad_request | Тело не JSON-объект или нет поля action |
| 401 | empty_key | Ключ не прислан |
| 401 | unknown_key | Ключ не найден |
| 401 | key_expired | Срок действия ключа истёк |
| 403 | forbidden_action | Ключу не разрешено это действие |
| 403 | club_foreign | club_id принадлежит другой организации |
| 404 | unknown_action | Такого действия нет |
| 404 | not_found | Объект не найден (клуб, станция) |
| 422 | bad_params | Не хватает обязательного параметра |
| 429 | rate_limited | Превышена частота (см. §7), в заголовке Retry-After |
| 500 | org_unavailable | База организации недоступна |
| 500 | query_failed | Сбой выборки. Не путайте с пустым списком: при сбое приходит именно ошибка, а не items: [] |
---
4. Действия
Что делать — говорит поле action. Точка входа одна, поэтому добавление метода не меняет адрес и не требует правок на стороне партнёра. Право каждого действия — это имя функции панели: оно подписано в карточке ключа рядом с пунктом прав, и именно его владелец отмечает, чтобы открыть партнёру действие.
Действия этого раздела только читают. Изменяющие — в §5.
Служебные (без права)
action | Назначение |
|---|---|
ping | Ключ принят: организация, имя ключа, область доступа, срок, время сервера |
meta.actions | Что доступно этому ключу — список действий и их прав |
Клуб и зал
action | Право | Параметры | Назначение |
|---|---|---|---|
club.list | spike_clubs_list | — | Клубы: название, адрес, телефоны, координаты |
club.map | spike_club_maps_list | club_id* | Схема зала (объект либо null, если карта не нарисована) |
gs_group.list | spike_gs_groups_list | club_id | Группы станций — на них держатся тарифы и абонементы |
gs.list | spike_gs_list | club_id, group_id, limit, offset | Станции: номер, тип, IP, MAC, группа, режим, частота экрана, версия клиентского ПО (app_version — её сообщает сама станция при подключении; пусто = не сообщала) |
gs.get | spike_gs_list | id или mac | Одна станция |
gs.status | spike_club_maps_list | club_id | Живое состояние зала (см. ниже) |
snap.list | spike_gs_snaps_list | club_id* | Золотые образы клуба: какой активен, когда снят |
Каталог
action | Право | Параметры | Назначение |
|---|---|---|---|
game.list | spike_games_list | club_id, genre_id, status, search, locale, limit, offset | Игры клуба: способ запуска, рейтинг, жанры |
game_genre.list | spike_game_genres_list | locale | Жанры организации (список один на сеть) |
banner.list | spike_banners_list | club_id | Баннеры: область на экране станции, период, окно времени |
category.list | spike_market_category_list | locale | Группы товаров |
product.list | spike_market_products_list | club_id*, cat_id, status, search, locale, limit, offset | Товары с ценой клуба и остатком |
product.get | spike_market_products_list | club_id*, id или code или barcode | Один товар (удобно со сканера) |
stock.list | spike_stock_list | club_id, type, product_id, date_from, date_to, limit, offset | Движение склада: приход, списание, продажа, возврат, корректировка |
search у товаров ищет по названию, артикулу и штрихкоду. locale подменяет name и description переводом этой локали; нет перевода — остаётся базовое значение, пустым ответ не приходит.
club_id у товаров обязателен: цена у товара своя в каждом клубе, и без клуба пришлось бы отдавать «цену вообще» — партнёр продавал бы по чужой. Если в этом клубе цены нет, приходит price: null и available: false — товар здесь не продаётся.
Время доступности товара приходит полем hours — по строке на день недели: [{day, from_sec, to_sec}], где day 0=Пн … 6=Вс, а from_sec/to_sec — секунды от полуночи. Пустой массив означает, что товар продаётся круглосуточно. to_sec — последняя включённая секунда (окно «18:00–22:00» приходит как 64800…79259), а to_sec меньше from_sec — это окно через полночь: отмеченный день задаёт начало, конец приходится на следующие сутки. Рядом идёт готовый ответ hours_open (true/false — продаётся ли прямо сейчас): считать это самому пришлось бы и по нашей конвенции границ, и в нашем часовом поясе. На поле available расписание не влияет — оно по-прежнему про цену в клубе и активность товара.
Тарификация
action | Право | Параметры | Назначение | |
|---|---|---|---|---|
tariff.list | spike_tariffs_list | club_id | Тарифы: цена за час, цвет, действуют ли скидки, status (0 = тариф выведен из обращения: в новые часы расписания его не назначают, но часы, где он уже стоит, считаются по нему) | |
tariff.schedule | spike_tariffs_schedule | club_id* | Недельная сетка: день+время → группа станций → тариф | |
ticket.list | spike_tickets_list | club_id | Абонементы: время, цена, auto_apply, status (0 = выключен клубом и не продаётся) | |
ticket.schedule | spike_tickets_schedule | club_id*, kind (sale \ | valid) | Когда абонемент продаётся либо когда его можно расходовать |
discount.list | spike_discounts_list | status | Справочник ситуативных скидок | |
promocode.list | spike_promocodes_list | status, search, club_id, limit, offset | Промокоды: событие, сумма, окно, остаток активаций | |
payment_method.list | spike_payment_methods_list | type, status, club_id | Кассы организации с вычисляемым балансом |
Границы интервалов расписания включительные, причём time_end — последняя включённая секунда блока (час 13 → 50399 = 13:59:59). В ответе рядом с секундами идут готовые from/to, чтобы не повторять эту конвенцию у себя и не терять последний час.
Две сетки абонемента — разные: kind=sale отвечает «когда его можно купить», kind=valid — «когда купленное можно тратить». Пустая сетка действия означает «действует всегда».
У бонусной кассы balance приходит null, а не ноль: бонусы — не живые деньги в кассе.
Клиенты и деньги
action | Право | Параметры | Назначение | ||
|---|---|---|---|---|---|
client.list | spike_clients_list | search, is_banned, limit, offset | Клиенты организации | ||
client.get | spike_clients_list | id или email или phone | Карточка клиента + его абонементы | ||
client.packs | spike_clients_list | id \ | email \ | phone | Только абонементы |
transaction.list | spike_transactions_list | client_id, club_id, type, date_from, date_to, limit, offset | Леджер операций | ||
shift.list | spike_cash_shifts_list | club_id, status, date_from, date_to, limit, offset | Кассовые смены с итогами и расхождением | ||
postpay.list | spike_cashdesk_list | club_id, status, date_from, date_to, limit, offset | Постоплаты |
search у клиентов ищет по почте, телефону, логину, имени и фамилии. Телефон в client.get ищется во всех форматах, в которых он мог сохраниться (380…, +380…, 0…, 38…) — импорт и регистрация писали его по-разному.
Служебные гостевые записи станций (гостевая постоплата) в выдачу клиентов не попадают. Заметки оператора и аватар наружу не отдаются.
Остаток абонемента считается по стенным часам: активированный абонемент горит с момента активации, поэтому remaining_seconds не покажет время, которое уже сгорело, пока клиент не играл.
Сессии, заказы, брони, чат
action | Право | Параметры | Назначение |
|---|---|---|---|
session.list | spike_gs_sessions_list | club_id, gs_id, client_id, status, date_from, date_to, limit, offset | История сессий |
session.get | spike_gs_sessions_list | id* | Сессия + журнал событий (старт, запуски игр, заказы, конец) |
order.list | spike_orders_list | club_id, status, gs_id, client_id, date_from, date_to, limit, offset | Заказы магазина с позициями |
order.get | spike_orders_list | id* | Один заказ; в options позиции — выбор клиента в комбо-наборе |
reserve.list | spike_gs_reserve_list | club_id, gs_id, client_id, date_from, date_to, limit, offset | Брони станций |
chat.list | spike_chats_list | club_id, date_from, date_to, limit, offset | Диалоги (один диалог = одна сессия) |
chat.messages | spike_chats_list | session_id*, limit, offset | Сообщения диалога |
У активной сессии итог ещё копится, поэтому в ответе есть active: true — не принимайте промежуточную сумму за окончательную.
Заказ не списывает баланс клиента — его платят на месте персоналу, поэтому total_amount это сумма к оплате, а не проведённый платёж.
Период у броней считается по пересечению, а не по началу: бронь, начавшаяся вчера и идущая сейчас, попадает в выборку за сегодня.
У диалогов total — число диалогов, а не сообщений.
Организация
action | Право | Параметры | Назначение |
|---|---|---|---|
org.info | spike_org_config_list | — | Часовой пояс, правила склада, лимиты абонементов и постоплаты, клубы с приёмом заказов |
locale.list | spike_locale_list | status | Языки каталога, какой по умолчанию |
staff.list | spike_staff_list | — | Сотрудники и их клубы |
event.list | spike_events_list | search, date_from, date_to, limit, offset | Журнал изменений в панели |
analytics.daily | spike_analytics_list | club_id, date_from, date_to | Предрасчёт по дням: занятость и выручка |
org.info не отдаёт реквизиты: ни доступов к БД, ни SMTP, ни токенов Telegram, ни gs_token станций, ни баланса организации. Право «просмотр настроек» выдаётся сотруднику внутри панели, а здесь ответ уходит третьей стороне.
event.list не отдаёт снимки «до/после»: в них попадают поля целиком, включая пароли Windows клуба. Остаётся кто, когда, в каком разделе и над чем.
analytics.daily считает ночной крон в часовом поясе организации, поэтому текущий день обычно пуст — это не потеря данных: за сегодня берите живые gs.status, transaction.list, order.list. Период по умолчанию — последние 30 дней.
Страницы и фильтры
limit— 50 по умолчанию, максимум 200;offset— с нуля. В ответе возвращаются оба.date_from/date_to—YYYY-MM-DDлибоYYYY-MM-DD HH:MM:SS. Дата без времени вdate_toозначает сутки целиком, иначе2026-10-01отрезал бы весь день, оставив полночь.- Значения фильтров-перечислений (
status,type,kind) сверяются с белым списком: чужое значение —422 bad_params, а не тихо проигнорированный фильтр.
Пример: живое состояние зала
→ { "action": "gs.status", "data": { "club_id": 3 } }
← { "code": 200, …, "data": { "items": [
{ "gs_id": 587, "club_id": 3, "number": "2",
"online": true, "mode": "active",
"busy": true, "client_id": 1402, "started_at": "2026-10-01 14:05:11",
"time_left": { "seconds": 4260, "text": "1:11", "unlimited": false } }
], "total": 38, "server_time": "2026-10-01 16:10:59" } }
| Поле | Смысл |
|---|---|
online | У станции открыто соединение с сервисом. Это про связь, не про режим |
mode | active / service (обслуживание) / admin (администрирование) / disabled (выведена из проката) |
busy | За станцией играет клиент. Станция может быть busy и при потерянной связи — сессию закроет реконсиляция |
time_left | Оценка остатка: бесплатные минуты клуба + применимые абонементы + деньги по действующей ставке. unlimited — ставки нет или она нулевая. null — за станцией никого нет |
time_left и online считаются тем же кодом, что и карта клуба, поэтому партнёр и оператор видят одну и ту же цифру.
---
5. Запись
Эти действия меняют данные клуба: принимают деньги, продают абонемент, двигают заказ, создают бронь, выдают скидку и кредит, шлют команду станции. Права у них свои — владелец отмечает их в карточке ключа отдельно от просмотра, поэтому «посмотреть» и «изменить» не выдаются одной галочкой.
Правила те же, что у оператора в панели: без открытой кассовой смены деньги не принимаются, абонемент сверх лимита организации не продаётся, бронь в закрытые часы не создаётся. Это не «похожие проверки» — обе стороны зовут один и тот же код, поэтому партнёр не может то, что запрещено оператору.
action | Право | Параметры | Что делает |
|---|---|---|---|
client.topup | spike_clients_topup | client_id*, amount*, payment_method_id*, comment, club_id | Пополняет баланс клиента через кассу |
client.sell_ticket | spike_clients_sell_ticket | client_id*, ticket_id* | Продаёт абонемент, списывая с баланса клиента |
transaction.refund | spike_transactions_refund | tx_id* | Возвращает пополнение |
order.set_status | spike_orders_list | order_id*, status* | Двигает заказ: new→processing→done, либо cancelled |
reserve.create | spike_gs_reserve_add | club_id*, gs_id*, date_start*, date_end*, client_id, description | Создаёт бронь станции |
reserve.cancel | spike_gs_reserve_del | reserve_id* | Снимает бронь |
discount.grant | spike_club_maps_gs_discount | gs_id*, discount_id* | Выдаёт ситуативную скидку (0 — снимает) |
credit.set | spike_club_maps_gs_credit | gs_id*, amount* | Ставит кредит на сессию (0 — снимает) |
postpay.enable | spike_cashdesk_postpay_enable | gs_id*, limit, guest, comment | Включает постоплату на станции |
postpay.limit | spike_cashdesk_postpay_enable | postpay_id*, limit | Меняет лимит постоплаты |
postpay.stop | spike_cashdesk_postpay_enable | postpay_id* | Завершает постоплату (станции уходит session_end) |
postpay.cancel | spike_cashdesk_postpay_enable | postpay_id* | Снимает постоплату, которая ждёт входа клиента |
postpay.pay | spike_cashdesk_postpay_enable | postpay_id*, payment_method_id*, comment | Принимает оплату постоплаты в кассу |
gs.command | spike_club_maps_gs_command | gs_id*, action*, data | Команда станции: message, poweroff, poweron, session_end |
Деньги
Касса обязательна (payment_method_id), и список её берут из payment_method.list. Для бонусной кассы обязателен comment — бонус без пояснения не принимаем, иначе на вопрос «за что начислено» ответить нечем. Если касса общая для организации, а клубов несколько, нужен club_id: без него операция потерялась бы для отчётов клуба.
Нужна открытая кассовая смена в этом клубе, иначе 409 no_shift. Смену открывает кассир в панели — это не обходится по API: деньги должны принадлежать чьей-то смене.
Возврат (transaction.refund) возможен только для пополнения и только один раз; списание разрешено «в минус» — деньги вернули из кассы, значит с баланса они уходят целиком.
Продажа абонемента списывает с баланса клиента, учитывая его перманентную скидку (если абонемент её допускает). Пакет кладётся в запас: часы стартуют при активации на станции, а не при продаже, иначе купленный впрок абонемент сгорел бы до прихода клиента.
Зал
Скидка: за станцией клиент — процент идёт снимком в его сессию (справочник потом правят, а человек доигрывает на обещанном); станция свободна — заявка ждёт того, кто сядет, 30 минут.
Кредит — разрешённая глубина минуса до конца сессии, а не зачисление денег: клиент доигрывает в долг и гасит его обычным пополнением. Нужна активная клиентская сессия.
Команды: разрешены только message, poweroff, poweron и session_end. Режимы станции, золотые образы и прочее управление залом остаются оператору — ими распоряжается клуб, а не внешняя система. В ответе delivery: done/delivered — станция приняла, offline — не на связи, timeout — не ответила за отведённое время.
Коды отказа записи
error_key | Когда |
|---|---|
bad_amount | Сумма не больше нуля (или отрицательный кредит) |
invalid_payment_method | Кассы нет или она выключена |
comment_required | Бонусная касса без комментария |
club_required | Касса общая, а club_id не передан |
no_shift | В клубе нет открытой кассовой смены |
client_not_found, ticket_not_found, order_not_found, reserve_not_found, tx_not_found | Объекта нет |
no_balance | На балансе клиента не хватает денег на абонемент |
ticket_disabled | Абонемент выключен клубом и не продаётся |
pack_single_only, pack_limit_reached | Лимиты организации на абонементы |
not_a_topup, already_refunded | Возврат: это не пополнение либо оно уже возвращено |
bad_status, order_finalized | Заказ: неизвестный статус либо он уже закрыт |
bad_time, past_time, too_far, too_short, too_long | Бронь: интервал не подходит |
outside_window, gs_busy, too_many_reserves | Бронь: вне часов клуба, время занято, слишком много броней |
no_client | На станции нет играющего клиента (кредит) |
discount_not_found, credit_unavailable | Скидки нет либо кредит в организации недоступен |
---
6. Права ключа
Область доступа ключа выбирает владелец:
- Полный доступ — ключ может всё, что может владелец организации;
- Группа доступа — права выбранной группы, как у сотрудника;
- Свой набор — только отмеченные функции.
Право каждого действия — это имя функции панели, и оно подписано в карточке ключа рядом с пунктом прав. То есть владелец видит ровно то, что сверяется при запросе: если партнёр получает forbidden_action на gs.list, в карточке ключа нужно отметить пункт с правом spike_gs_list.
Срок действия ключа проверяется на сервере: истёк — любой запрос отвечает key_expired (401), даже если партнёр об этом не знает.
---
7. Частота запросов
Лимит — 120 запросов в минуту на ключ (значение меняет администратор системы). При превышении приходит 429 rate_limited и заголовок Retry-After: 60.
Опрос состояния зала раз в 2–5 секунд в лимит укладывается с запасом. Если нужно чаще — попросите владельца клуба поднять лимит, а не обходите его несколькими ключами: лимит считается на ключ именно для того, чтобы один партнёр не мешал другим.
---
8. Журнал обращений
Владелец может включить в карточке ключа журнал обращений: тогда каждый вызов по этому ключу хранится 7 дней — действие, код ответа, длительность, IP и тело запроса (ключ из тела вырезается). Это инструмент разбора «у партнёра не работает»: постоянно журнал не нужен.
Если ваш запрос получает отказ, а причина непонятна, попросите владельца включить журнал и повторите запрос — в журнале будет видно и действие, и код, и то, что именно вы прислали.
---
9. Чего в этом API нет
Правки справочников. Тарифы, абонементы, товары, клубы, станции, сотрудники, права, настройки организации по API не меняются — это делают в панели. Партнёру нужно работать с клиентами и залом, а не переписывать настройки клуба, и каждая такая правка требовала бы своих проверок формы.
Список ключей. По ключу нельзя прочитать spike_apikeys: партнёр увидел бы ключи других партнёров этой же организации.
Структуру прав панели (access_groups_list) не отдаём: внешней системе она ни о чём не говорит, а в ответе появлялись бы имена наших внутренних функций.
Чувствительные поля вырезаны даже там, где право на раздел есть: реквизиты БД, SMTP и токены ботов (org.info), пароли Windows клуба и снимки «до/после» (event.list), заметки оператора о клиенте (client.*), пароли сотрудников (staff.list). Из персональных данных gs.status отдаёт только client_id — без имени, телефона и почты.
---
10. Вебхуки: события сами приходят к вам
Чтобы не опрашивать API в поисках нового заказа, укажите владельцу клуба свой адрес — и события будут приходить на него сами. Настраивается в панели организации (карточка «Клиенты и заказы»): адрес, выключатель, ключ подписи и список событий.
Запрос: POST <ваш адрес>, тело — JSON:
{ "event": "order.new",
"org_id": 11,
"sent_at": "2026-10-08T12:46:08+03:00",
"data": { "order_id": 42, "club_id": 1, "gs_id": 11, "client_id": 35839,
"total": 320.00, "items": 3 } }
Заголовки: X-Spike-Event (имя события), X-Spike-Org (организация) и X-Spike-Signature — подпись тела.
События
| Событие | Когда | Что в data |
|---|---|---|
order.new | клиент создал заказ на станции | order_id, club_id, gs_id, client_id, total, items |
order.status | статус заказа изменён | order_id, club_id, gs_id, old_status, status, total, user_login |
session.start | клиент авторизовался на станции | session_id, club_id, gs_id, client_id, auth_method |
session.end | сессия закрыта | то же плюс date_start, date_end, duration_seconds, time_cost, products_cost, total_cost, reason |
client.topup | пополнение баланса клиента | client_id, login, amount, balance, club_id, payment_method, type, comment, source |
Организация может подписаться на часть событий — тогда придут только они.
Подпись: проверяйте её
X-Spike-Signature: sha256=<hex> — это HMAC-SHA256 от тела запроса целиком на ключе, который владелец клуба задал в настройках. Считайте подпись сами и сравнивайте с заголовком: без этой проверки на ваш адрес сможет писать кто угодно, выдавая себя за нас. Ключ не задан — заголовка не будет; просить владельца задать его стоит в любом случае.
# пример проверки (bash)
calc=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
[ "sha256=$calc" = "$SIG" ] || exit 1
Доставка
Отправку делает очередь, а не веб-запрос оператора: панель и сокет-сервис никогда не ждут ваш сервер. Отсюда правила, на которые стоит рассчитывать:
- таймауты — 5 секунд на соединение и 10 на весь обмен;
- три попытки с паузами (сразу, через 2 и через 5 секунд), пока ответ не
2xx; - ответ
4xx(кроме408и429) повторять не будем — считаем, что запрос отвергнут; - редиректы не выполняются:
Locationигнорируется, POST по другому адресу не уйдёт; - порядок событий не гарантирован, доставка как минимум один раз не гарантируется — если ваш сервер лежал все три попытки, событие потеряется. Критичное сверяйте чтением API: в нём есть и заказы, и сессии, и операции.
Отвечайте 200 как можно быстрее и обрабатывайте событие у себя асинхронно: пока вы держите соединение, задача очереди занята вами.