Тестируйте вебхуки локально — без сервера, за 3 минуты

Используйте ngrok, чтобы получать webhook-уведомления ApiPay прямо на localhost. Видите запросы в реальном времени — без деплоя.

Зачем это нужно

При разработке у вас нет публичного URL — ApiPay не может отправить webhook на localhost:3000. ngrok создаёт безопасный HTTPS-тоннель к вашему компьютеру и даёт публичный URL, доступный из интернета.

А встроенный Inspect UI (localhost:4040) показывает все входящие запросы — заголовки, тело, статус. Это идеальный инструмент для отладки вебхуков, даже если у вас ещё нет сервера.

5 шагов — и вебхуки приходят на localhost

1

Установите ngrok

Через Homebrew (macOS) или скачайте с сайта:

# macOS $ brew install ngrok # Или скачайте: https://ngrok.com/download

Зарегистрируйтесь на ngrok.com (бесплатно) и добавьте токен:

$ ngrok config add-authtoken ВАШ_ТОКЕН
2

Запустите тоннель

Укажите порт, на котором работает ваш локальный сервер (например, 3000):

$ ngrok http 3000 Session Status online Forwarding https://a1b2c3d4.ngrok-free.dev -> http://localhost:3000

Скопируйте HTTPS URL — это ваш публичный адрес. Он будет работать, пока ngrok запущен.

Совет: URL меняется при каждом перезапуске ngrok. Для постоянного домена используйте ngrok http --url=ваш-домен.ngrok-free.dev 3000 (один бесплатный домен в аккаунте).
3

Вставьте URL в ApiPay

Перейдите в ApiPay.kz → Настройки → Подключение и вставьте ngrok URL как Webhook URL:

https://a1b2c3d4.ngrok-free.dev/webhook

Нажмите «Сохранить».

4

Отправьте тестовый вебхук

В настройках API ключа нажмите кнопку «Тест webhook». ApiPay отправит тестовое событие webhook.test на ваш ngrok URL.

Готово! Если ваш сервер запущен — он получит POST-запрос. Если нет — не страшно, переходите к шагу 5.
5

Откройте Inspect UI — видите всё

Перейдите в браузере на localhost:4040. Это встроенная панель ngrok — показывает все входящие запросы:

  • Заголовки (включая X-Webhook-Signature)
  • Тело запроса (JSON)
  • HTTP-статус ответа
  • Время ответа
Даже без сервера — Inspect UI покажет входящие запросы. ngrok вернёт 502 (потому что localhost не отвечает), но вы увидите, что именно отправил ApiPay. Это отличный способ понять формат вебхука до написания кода.

Webhook API — справочник

События

Событие Когда
invoice.status_changed Статус счёта изменился (pending → paid, cancelled, expired)
invoice.refunded Создан возврат по оплаченному счёту
subscription.payment_succeeded Успешный платёж по подписке
subscription.payment_failed Неуспешный платёж по подписке
subscription.grace_period_started Начался grace-период подписки
subscription.expired Подписка истекла после grace-периода
webhook.test Тестовое событие (кнопка «Тест webhook»)

Формат запроса

POST /webhook HTTP/1.1 Content-Type: application/json X-Webhook-Signature: <HMAC-SHA256 подпись тела> { "event": "invoice.status_changed", "invoice": { "id": 42, "external_order_id": "order_abc", "status": "paid", "amount": "15000.00", "paid_at": "2026-01-15T10:30:00Z" }, "source": "my-api-key" }

Верификация подписи (HMAC-SHA256)

Каждый запрос содержит заголовок X-Webhook-Signature — HMAC-SHA256 от тела запроса. Секрет вы получаете при создании webhook в настройках.

Обязательно проверяйте подпись в production. В тестовом режиме можно пропустить, но в production без проверки злоумышленник может отправить фальшивый webhook.

Retry-политика

Если ваш сервер не вернул 2xx, ApiPay повторит запрос до 3 раз с экспоненциальной задержкой (1 мин, 5 мин, 15 мин). После 3 неудач webhook помечается как failed.

Минимальный сервер для вебхуков

Скопируйте и запустите — принимает вебхуки, проверяет подпись, логирует в консоль

server.js — Node.js (без зависимостей)
const http = require('http') const crypto = require('crypto') const PORT = 3000 const WEBHOOK_SECRET = 'ваш_секрет_из_настроек' // ApiPay → Настройки → Подключение const server = http.createServer((req, res) => { if (req.method === 'POST' && req.url === '/webhook') { let body = '' req.on('data', chunk => { body += chunk }) req.on('end', () => { // Проверка подписи const signature = req.headers['x-webhook-signature'] const expected = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(body) .digest('hex') if (signature !== expected) { console.log('!! Подпись не совпадает') res.writeHead(401) return res.end('Invalid signature') } // Обработка события const data = JSON.parse(body) console.log('\n--- Webhook получен ---') console.log('Событие:', data.event) console.log('Данные:', JSON.stringify(data, null, 2)) console.log('---\n') res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ status: 'ok' })) }) } else { res.writeHead(404) res.end('Not found') } }) server.listen(PORT, () => { console.log(`Webhook сервер запущен: http://localhost:${PORT}/webhook`) console.log('Запустите ngrok: ngrok http ' + PORT) })
Запуск: node server.js — затем в другом терминале ngrok http 3000. Вебхуки будут приходить и логироваться в консоль.

Часто задаваемые вопросы

ngrok бесплатный?
Да. Бесплатный план включает 1 бесплатный dev-домен, до 3 эндпоинтов, 20 000 HTTP-запросов в месяц и 1 GB трафика. Для тестирования вебхуков этого более чем достаточно. Платные планы от $8/мес добавляют кастомные домены и больше эндпоинтов.
Нужна ли кредитная карта?
Для HTTP/HTTPS-тоннелей — нет. Карта нужна только для TCP-эндпоинтов (верификация, списаний нет). Для тестирования вебхуков используются HTTP-тоннели, так что карта не потребуется.
Что показывает localhost:4040?
Это ngrok Inspect UI — веб-панель, где видны все HTTP-запросы, прошедшие через тоннель. Для каждого запроса показаны: метод, URL, заголовки, тело запроса, HTTP-статус ответа и время. Можно повторить (replay) любой запрос. Работает даже если ваш сервер не запущен — вы увидите 502-ответы, но тело запроса будет видно.
URL меняется при перезапуске — это проблема?
На бесплатном плане ngrok даёт один постоянный dev-домен. Используйте его: ngrok http --url=ваш-домен.ngrok-free.dev 3000. Тогда URL не будет меняться и не придётся обновлять webhook URL в ApiPay каждый раз.
Есть ли альтернативы ngrok?
Да: LocalXpose, Cloudflare Tunnel (бесплатно), Loophole. Принцип тот же — создают тоннель к localhost. ngrok самый популярный и имеет удобный Inspect UI.
Это безопасно? Мой localhost доступен из интернета?
ngrok создаёт тоннель только к указанному порту. Остальные порты и файлы остаются недоступными. Тоннель работает только пока ngrok запущен — после закрытия URL перестаёт работать. Для production используйте настоящий сервер с HTTPS.