Solana не має вбудованого механізму webhooks на рівні валідатора. Усі webhook-підписки в екосистемі реалізуються через проміжні шари: сторонні RPC-провайдери з індексацією або self-hosted рішення на базі Geyser Plugin із власним реле-сервісом. Незалежно від джерела, інженерне завдання зводиться до одного: надійно приймати, валідувати та обробляти push-сповіщення про події в ланцюжку без втрати даних і з передбачуваною поведінкою за навантаження.
Цей матеріал описує webhook-шар як споживач даних: від конфігурації підписок до обробки черги та діагностики типових збоїв. Приклади наведені для загального патерну інтеграції, який підтримують більшість індексаторів у Solana-екосистемі.
Налаштування webhook-підписок
Конфігурація webhook-підписки зазвичай складається з двох частин: реєстрація ендпоінта у провайдера та налаштування фільтрів, що визначають, які саме події тригеритимуть відправку. Головне інженерне правило — фільтрувати на стороні джерела, а не на своєму сервері.
Фільтрація за програмами та подіями
Типовий запит на реєстрацію webhook містить цільовий URL, типи подій та об'єкти фільтрації. У Solana-контексті фільтри зазвичай оперують такими параметрами:
- programId — адреса програми, транзакції через яку тригерять webhook. Це найбільш ефективний фільтр, оскільки індексатор може відсікати події на рівні парсингу слоту.
- accountKeys — конкретні акаунти, зміна стану яких є сигналом. Корисно для відстеження PDA (Program Derived Address) конкретного користувача або пулу.
- eventType — рівень гранулярності: транзакція в цілому, зміна стану акаунта, конкретний лог-інструкції (instruction log).
Приклад структури фільтра для відстеження успішних транзакцій через конкретну програму:
Формат залежить від провайдера. Наведена структура ілюструє загальний патерн, а не конкретний API.
- URL:
https://your-domain.com/webhooks/solana - Webhook type:
transaction - Filters:
programId = "YourProgramAddress...",status = "confirmed"
Критерій вибору рівня фільтрації — співвідношення обсягу зайвих подій до складності підтримки. Якщо програма генерує тисячі транзакцій на добу, але вам потрібні лише ті, що змінюють конкретний PDA, додайте фільтр за accountKeys. Це зменшить навантаження на ваш ендпоінт і знизить витрати на обробку.
Аутентифікація та перевірка підписів
Кожен webhook-запит має бути верифікований. Стандартний механізм — HMAC-підпис у заголовку HTTP-запиту. Провайдер генерує підпис корисного навантаження (payload) за допомогою секретного ключа, який ви отримуєте при реєстрації webhook.
Очікуваний порядок перевірки на стороні вашого сервісу:
- Витягнути значення заголовка з підписом (наприклад,
X-Webhook-Signature). - Отримати raw body запиту до будь-якого парсингу — підпис обчислюється на сирих байтах.
- Обчислити HMAC-SHA256 від raw body з використанням вашого секретного ключа.
- Порівняти обчислений підпис із отриманим за допомогою timing-safe функції порівняння.
Не використовуйте звичайний оператор рівності для порівняння підписів — це відкриває вектор для timing-атак. У мовах із стандартною бібліотекою криптографії завжди є спеціалізована функція (наприклад, crypto.timingSafeEqual у Node.js або constant_time_compare у Python).
Якщо перевірка підпису не проходить, поверніть HTTP 401 і залогуйте подію. Не обробляйте payload без валідного підпису навіть якщо формат виглядає коректним.
Обробка повторних спроб та черги
Webhook-інфраструктура за визначенням працює в умовах ненадійної мережі. Ваш сервер може бути недоступний, перевантажений або повільно відповідати. Провайдери зазвичай реалізовують механізм повторних спроб (retry), але ваша система має бути готова до цього коректно.
Базова архітектура обробки webhook має виглядати так:
- Ендпоінт прийому — мінімальна логіка: верифікація підпису, запис у чергу, миттєва відповідь HTTP 200.
- Черга повідомлень — буфер між прийомом та обробкою. Це може бути Redis, RabbitMQ, SQS або навіть стійка черга в пам'яті з персистентністю на диск для невеликих обсягів.
- Worker обробки — витягує повідомлення з черги, виконує бізнес-логіку, позначає як оброблене.
Чому синхронна обробка прямо в ендпоінті є помилкою: якщо бізнес-логіка включає виклики до БД, зовнішніх API або обчислення, час відповіді зростає. Провайдер інтерпретує повільну відповідь як потенційний збій і може відправити дублікат, створюючи race condition.
Ідемпотентність — ключова властивість обробки. Кожен webhook у Solana-контексті зазвичай містить сигнатуру транзакції (transactionSignature). Використовуйте її як ідемпотентний ключ: перед обробкою перевіряйте, чи вже оброблялася ця сигнатура. Зберігайте стан у БД або кеші з TTL, достатнім для вікна повторних спроб провайдера.
Стратегія backoff для повторних спроб стосується як провайдера (коли він повторює відправку вам), так і вашого воркера (коли він повторює обробку повідомлення з черги). Для воркера рекомендований підхід — exponential backoff із jitter і обмеженою кількістю спроб. Після вичерпання спроб повідомлення має потрапити в dead letter queue (DLQ) для ручного розгляду, а не безкінечно блокувати чергу.
Порядок подій. Webhooks не гарантують строгий порядок доставки. Дві транзакції в одному слоті можуть надійти в різному порядку. Якщо порядок має значення для вашої бізнес-логіки, використовуйте поле slot або blockTime з payload для впорядкування на стороні воркера перед обробкою.
Типові помилки інтеграції webhooks
- Відсутність ідемпотентності. Найчастіша причина дублювання даних. Провайдер відправляє повторно, ваш сервіс обробляє двічі. Результат — подвоєння балансів, дублікати записів, некоректний стан. Фактична причина: відсутність перевірки унікальності по сигнатурі транзакції перед виконанням бізнес-логіки.
- Парсинг body до перевірки підпису. Якщо ваш фреймворк автоматично десеріалізує JSON у об'єкт, ви втрачаєте raw body. Підпис, обчислений на десеріалізованих даних, не збігається з оригінальним. Рішення: читати raw body як буфер і верифікувати підпис до будь-якої десеріалізації.
- Ігнорування статусу підтвердження транзакції. Деякі провайдери відправляють webhooks для транзакцій на різних етапах:
processed,confirmed,finalized. Якщо ваш сервіс реагує наprocessedяк на фінальний стан, існує ризик обробки відкоченої транзакції. Фільтруйте за рівнем підтвердження, який відповідає вашій моделі ризику. - Блокуюча обробка в ендпоінті. Синхронний виклик БД або зовнішнього сервісу прямо в обробнику webhook. За високого навантаження це призводить до таймаутів на стороні провайдера, хибних повторних спроб та каскадних збоїв. Розділяйте прийом та обробку чергою.
- Неврахування розміру payload. Транзакції з великою кількістю інструкцій або log-виводом генерують великі webhook-тіла. Якщо ваш сервер має обмеження на розмір запиту (наприклад, nginx за замовчуванням обмежує body до 1 МБ), великі webhooks будуть відхилені з HTTP 413. Перевірте та за потреби збільште ліміти на всіх рівнях: reverse proxy, application server, framework.
- Відсутність моніторингу доставки. Без логування прийнятих webhook (хоча б сигнатури та timestamp) ви не зможете діагностувати пропущені події. Мінімум: логувати сигнатуру транзакції, HTTP-статус відповіді та час обробки для кожного webhook.
Безпечний наступний крок після налаштування
Після того як webhook-підписка налаштована, підписи верифікуються, а черга працює, наступний крок — валідація під навантаженням до повного переходу на production.
Паралельне порівняння з polling. Запустіть webhook-канал паралельно з існуючим polling-механізмом (якщо він є) і порівнюйте результати протягом значного періоду. Різниця в кількості оброблених подій вказує на пропуски або дублікати. Цей етап триває доти, доки розбіжності не будуть пояснені та усунуті.
Моніторинг ключових метрик:
- Час від появи слоту в ланцюжку до отримання webhook (latency).
- Кількість повідомлень у DLQ за період.
- Співвідношення прийнятих до унікально оброблених подій (виявлення дублікатів).
- Час відповіді ендпоінта прийому (p99).
План відкату. Зберігайте можливість швидкого переключення на polling або інше джерело даних. Webhook-підписка має бути конфігурована таким чином, щоб її можна було деактивувати без впливу на інші компоненти системи. Якщо ваш сервіс перестає відповідати, провайдер зазвичай автоматично призупиняє відправку після серії невдалих спроб — переконайтеся, що ви отримуєте сповіщення про такі події.
Коли webhook-шар стабільно працює і ви потребуєте нижчої затримки або повнішого контролю над потоковими даними, наступний логічний крок — перехід на власне стрімінгове рішення. Детально це розглянуто в матеріалі про те, як налаштувати Geyser Plugin для стрімінгу даних.