Розробка callback/webhook сповіщень про платежі

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка callback/webhook сповіщень про платежі
Простий
~2-3 дні
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1307
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1219
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1148
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    611
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    886

Розробка callback/webhook уведомлень про платежі

Платіж зафіксований у блокчейні — означає, його треба обробити у вашій системі. Проблема: блокчейн не може "дзвонити" у ваш бекенд. Є два підходи: polling (ви запитуєте) та push (зовнішня система повідомляє вас). Webhook — це push-уведомлення від платіжного процесора або вашого власного моніторингу блокчейну.

Архітектура webhook-системи

Типова схема для крипто-платежів:

Blockchain → Monitoring service → Webhook dispatcher → Your app endpoint
                                        ↓
                                  Retry queue (Redis/DB)

Monitoring service — це або стороння послуга (Alchemy Notify, Moralis Streams, QuickNode Streams), або ваш власний процес, що слухає ноду.

Отримання webhook від Alchemy Notify

// Створення webhook через Alchemy API
const response = await fetch('https://dashboard.alchemy.com/api/create-webhook', {
  method: 'POST',
  headers: {
    'X-Alchemy-Token': process.env.ALCHEMY_AUTH_TOKEN!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    network: 'ETH_MAINNET',
    webhook_type: 'ADDRESS_ACTIVITY',
    webhook_url: 'https://yourapp.com/webhooks/crypto',
    addresses: ['0xYourAddress'],
  }),
})

Ваш webhook endpoint: правильна обробка

Головне правило: webhook endpoint повинен відповідати 200 якомога швидше. Всю важку логіку — в чергу:

// Express.js endpoint
app.post('/webhooks/crypto', express.raw({ type: 'application/json' }), async (req, res) => {
  // 1. Верифікувати підпис — ДО будь-якої обробки
  const signature = req.headers['x-alchemy-signature'] as string
  const isValid = verifyAlchemySignature(req.body, signature, process.env.WEBHOOK_SIGNING_KEY!)
  if (!isValid) {
    return res.status(401).send('Invalid signature')
  }

  // 2. Відповісти 200 негайно
  res.status(200).send('OK')

  // 3. Поставити в чергу для асинхронної обробки
  const payload = JSON.parse(req.body.toString())
  await jobQueue.add('process-crypto-payment', payload, {
    attempts: 5,
    backoff: { type: 'exponential', delay: 2000 },
  })
})

function verifyAlchemySignature(body: Buffer, signature: string, signingKey: string): boolean {
  const hmac = crypto.createHmac('sha256', signingKey)
  hmac.update(body)
  const digest = hmac.digest('hex')
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest))
}

timingSafeEqual обов'язковий — без нього timing attack дозволить підібрати підпис за розумний час.

Ідемпотентність: обробляти повторення коректно

Webhook-провайдери гарантують доставку at-least-once, не exactly-once. Ваш обробник повинен бути ідемпотентним:

async function processPaymentWebhook(txHash: string, address: string, amountWei: bigint) {
  // Використовуємо INSERT ... ON CONFLICT DO NOTHING
  const result = await db.query(`
    INSERT INTO processed_webhooks (tx_hash, processed_at)
    VALUES ($1, NOW())
    ON CONFLICT (tx_hash) DO NOTHING
    RETURNING id
  `, [txHash])

  if (result.rowCount === 0) {
    // Вже оброблено — пропускаємо, не помилка
    return
  }

  // Основна логіка обробки платежу
  await updatePaymentStatus(address, amountWei, txHash)
}

Retry-механізм для вихідних webhook (якщо ваш сервіс повідомляє інші)

Якщо ваш сервіс сам відправляє webhook в системи клієнтів:

interface WebhookDelivery {
  id: string
  url: string
  payload: object
  attempt: number
  nextRetryAt: Date
}

async function deliverWebhook(delivery: WebhookDelivery): Promise<void> {
  try {
    const res = await fetch(delivery.url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Webhook-Signature': signPayload(delivery.payload),
        'X-Webhook-ID': delivery.id,
        'X-Webhook-Attempt': String(delivery.attempt),
      },
      body: JSON.stringify(delivery.payload),
      signal: AbortSignal.timeout(10_000), // 10 секунд таймаут
    })

    if (!res.ok) {
      throw new Error(`HTTP ${res.status}`)
    }

    await db.markDelivered(delivery.id)
  } catch (err) {
    const nextAttempt = delivery.attempt + 1
    if (nextAttempt > 10) {
      await db.markFailed(delivery.id, String(err))
      return
    }

    // Експоненціальний backoff: 30s, 1m, 2m, 5m, 10m, ...
    const delayMs = Math.min(30_000 * Math.pow(2, nextAttempt - 1), 3_600_000)
    await db.scheduleRetry(delivery.id, nextAttempt, new Date(Date.now() + delayMs))
  }
}

Схема retry: 10 спроб з експоненціальним backoff достатньо для більшості випадків. Фінальний провал — повідомити розробникам, зберегти у dead letter queue.

Таблиця для зберігання доставок

CREATE TABLE webhook_deliveries (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  event_type    VARCHAR(50) NOT NULL,
  payload       JSONB NOT NULL,
  target_url    TEXT NOT NULL,
  status        VARCHAR(20) DEFAULT 'pending',
  attempt       INTEGER DEFAULT 0,
  next_retry_at TIMESTAMPTZ,
  created_at    TIMESTAMPTZ DEFAULT NOW(),
  delivered_at  TIMESTAMPTZ,
  last_error    TEXT
);

CREATE INDEX ON webhook_deliveries(status, next_retry_at)
  WHERE status IN ('pending', 'retrying');

Partial index за статусами — воркер при polling читає лише активні записи, не сканує доставлені.

Безпека: що обов'язково

  • HMAC-SHA256 підпис на кожен вихідний webhook з секретом клієнта
  • Timestamp у payload (поле sent_at) та перевірка на стороні отримувача, що webhook не старше 5 хвилин — захист від replay attacks
  • HTTPS only — не доставляти на HTTP endpoints
  • Ідемпотентний ключ у заголовку (X-Webhook-ID) — отримувач може дедуплікувати

Строки реалізації: базовий webhook endpoint з верифікацією підпису та чергою — 1 день. Повнофункціональна система з retry, dead letter queue, dashboard для моніторингу доставок, управлінням підписками — 2–3 дні.