Партнерський 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-ключі → «Створити». Там само він вибирає, що ключу доступно і до якого моменту він працює, тому просити ключ потрібно разом із перелiком потрібних вам дій — власник позначить саме їх.
Що сказати власникові:
- які дані потрібні — за діями з §4 (наприклад
gs.status,product.list); у картці ключа він позначає права з тими самими назвами, що підписані біля дії; - на який термін — ключ можна видати на день, тиждень, місяць, рік, до конкретної дати або безстроково;
- чи потрібен журнал звернень — якщо інтеграцію ще налагоджують, власник вмикає позначку й бачить ваші запити (див. §8).
Готовий ключ він передає вам як пароль. У списку ключів він видний маскою (перші та останні чотири символи), повністю — у картці, звідки його й копіюють.
Докладніше з боку власника — у посібнику користувача SpikePC, розділ API-ключі.
Ключ не привʼязаний до клубу: він діє на всю організацію, а конкретний клуб ви вказуєте параметром club_id.
---
2. Транспорт
POST https://spikepc.com/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 не змінюються — це роблять у панелі. Партнерові потрібно працювати з клієнтами та залом, а не переписувати налаштування клубу, і кожна така правка вимагала б своїх перевірок форми.
Списку ключів. За ключем не можна прочитати таблицю ключів: партнер побачив би ключі інших партнерів цієї ж організації.
Структуру прав панелі не віддаємо: зовнішній системі вона ні про що не говорить, а у відповіді з'являлися б назви наших внутрішніх функцій.
---
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 якнайшвидше та обробляйте подію в себе асинхронно: поки ви тримаєте з'єднання, задача черги зайнята вами.