Вебхуки (Webhooks)
Коли транзакція, синхронізована з Quasar, досягає термінального статусу, Quasar надсилає захищений HTTP POST-запит на всі зареєстровані ендпоінти вашого додатку. Кожен запит підписується унікальним секретом ендпоінта за алгоритмом HMAC-SHA256, що дозволяє вашому бекенду безпечно оновлювати замовлення, баланси та надсилати сповіщення без постійного опитування блокчейну (polling).
🛠️ Реєстрація ендпоінта
У панелі керування перейдіть у розділ Webhooks та додайте ендпоінт для відповідного додатку:
| Поле | Призначення та правила |
|---|---|
| URL | Куди Quasar надсилатиме сповіщення. Обов'язково за протоколом https://, за винятком адреси localhost для локальної розробки. |
| Events | Маска * (всі події), або окремі події: transaction:success, transaction:failed, transaction:replaced. |
| Transaction type filter | * для всіх типів операцій або точний тип транзакції з вашого Pulsar store (наприклад, SWAP, BRIDGE, STAKE). |
| Active | Неактивні ендпоінти не отримують нових доставок і спроб повтору. |
Quasar генерує секрет підпису (signing secret): рядок із префіксом whsec_ та 64 шістнадцятковими символами (32 криптографічні випадкові байти). Він зберігається в зашифрованому вигляді. Один додаток може мати до 500 ендпоінтів.
📨 Формат запиту та корисне навантаження
POST /api/webhooks/quasar HTTP/1.1Content-Type: application/jsonUser-Agent: Quasar-Webhook-Worker/1.0 (TuwaIO)x-quasar-signature: 5f3c… (hex HMAC-SHA256 of the body)x-quasar-event: Success{ "txKey": "0x3a5f…", "hash": "0x3a5f…", "status": "Success", "action": "Success", "txType": "SWAP", "chainId": "1", "timestamp": 1790000000, "metadata": { "tokenIn": "USDC", "tokenOut": "ETH", "amount": 100 }}| Поле тіла | Опис даних |
|---|---|
txKey, hash | Унікальний ключ транзакції в Pulsar та її ончейн-хеш у мережі (якщо він уже відомий). Для пакета EIP-5792 txKey — це ідентифікатор пакета, а hash — транзакція, що його виконала. |
status, action | Фінальний статус: Success, Failed або Replaced; також дублюється у заголовку x-quasar-event. |
txType | Тип операції згідно з вашою бізнес-логікою в Pulsar. |
chainId | Мережа виконання у форматі CAIP-2: наприклад, "1" для Ethereum, або "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" для Solana mainnet. |
timestamp | Час Unix (в секундах), коли було сформовано вебхук. |
replacedTxHash | Для Replaced: транзакція, яка зайняла nonce цієї, якщо її знайдено. |
replacedReason | Для Replaced: repriced (та сама транзакція з вищою комісією, Speed up у гаманці), cancelled, replaced (інша транзакція) або unknown (її nonce зайняла транзакція, яку не вдалося знайти, тож кошти могли піти). |
metadata | Повний вихідний об'єкт payload, переданий клієнтом у Pulsar. |
🔐 Верифікація підпису HMAC
Заголовок x-quasar-signature містить шістнадцятковий HMAC-SHA256 від сирого тіла HTTP-запиту, підписаний вашим whsec_.... Розраховуйте підпис строго над сирим текстом до парсингу в JSON та порівнюйте у сталий час:
import { createHmac, timingSafeEqual } from 'node:crypto'; export async function POST(request: Request) { const secret = process.env.QUASAR_WEBHOOK_SECRET; const signature = request.headers.get('x-quasar-signature'); if (!secret || !signature) { return Response.json({ error: 'Missing signature' }, { status: 401 }); } const body = await request.text(); const expected = Buffer.from(createHmac('sha256', secret).update(body).digest('hex'), 'hex'); const received = Buffer.from(signature, 'hex'); if (received.length !== expected.length || !timingSafeEqual(received, expected)) { return Response.json({ error: 'Invalid signature' }, { status: 401 }); } const event = JSON.parse(body) as { txKey: string; status: string; txType: string; metadata: unknown }; if (event.status === 'Success') { // Fulfill the order, credit the balance, notify the user... } return Response.json({ received: true });}Коли транзакція завершується невдачею:
- Solana: транзакція з помилкою в мережі отримує
Failedодразу після підтвердження, без очікування фіналізації. Підпис, який так і не потрапив у мережу, отримуєFailed, щойно мережа минає йогоlastValidBlockHeight(Pulsar зберігає його для транзакцій, надісланих черезsignAndSendSolanaTx; помилка каже, що blockhash застарів), інакше — через годину після синхронізації. - Пакети EIP-5792 отримують
Failedчерез годину після синхронізації, якщо ваш застосунок так і не надіслав хеш їхньої транзакції (посібник із синхронізації).
2xx якомога швидше: Quasar очікує відповіді максимум 10 секунд. Тривалі операції (відправку листів, генерацію звітів) виносьте у фонові черги. Обробник повинен бути ідемпотентним за парою (txKey, status).🔁 Повтори доставок та аудит
- Якщо доставка не вдалася (таймаут, помилка мережі чи статус, відмінний від
2xx), система автоматично повторює запит до 5 разів з експоненційним інтервалом (від 1 хвилини). - Усі спроби фіксуються в журналі Webhook Deliveries у панелі керування. Будь-які секретні ключі автоматично маскуються в тілі відповідей.
- Ви можете вручну повторити невдалу доставку прямо зі списку записів у панелі, не частіше разу на хвилину.
- Трекер може втратити транзакцію з поля зору, а вузол забуває замінений хеш. Коли Quasar згодом знаходить, що її включено в блок або замінено, запис виправляється, і ендпоінт отримує ще один вебхук із новим
status: вважайте результатом останній. - Журнали доставок зберігаються до 3 місяців. Для довшого аудиту зберігайте події, які отримує ваш ендпоінт.
- Кожна доставка списує лише 1 одиницю квоти (лише за першу спробу, повтори безкоштовні; дивіться Квоти та ліміти).
💻 Локальна розробка без ngrok
Хмарний Quasar не має прямого доступу до вашого localhost. Для локального тестування додайте ендпоінт із адресою http://localhost:3000/api/webhooks/quasar. Quasar Cloud автоматично зберігатиме події для локального тунелю local relay утиліти CLI quasar-sdk, яка отримує їх через захищений потік Server-Sent Events і пересилає на ваш локальний сервер з оригінальними заголовками підпису:
# .env.local: QUASAR_WEBHOOK_SECRET=whsec_... (the secret of the localhost endpoint)npx @tuwaio/quasar-sdk listen --forward-to http://localhost:3000/api/webhooks/quasar