Точка входа для вебхуков на вашем домене
Направьте Stripe, GitHub или Slack на URL с hostname, который контролируете вы, — DNS и TLS в вашем аккаунте. Проверяйте подписи по сырому телу, подтверждайте в лимите провайдера и продолжайте тяжёлую работу в serverless-функциях или пайплайнах на Inquir.
Обновлено: 2026-06-28
Кратко
Суть ответа
Точка входа для вебхуков на вашем домене. Вы приносите домен и DNS; Inquir запускает изолированные serverless-функции за маршрутами шлюза. У каждого провайдера — свой обработчик с узкими правами, а не один эндпоинт, где смешаны cookie и секреты вебхуков.
Когда подходит
- Нужен стабильный HTTPS на hostname под вашим контролем (свой домен + DNS) и serverless-обработчики на Inquir.
- Хочется развести трафик вебхуков и пользовательские API одного продукта.
На что обратить внимание
- Один эндпоинт на VPS смешивает ритм деплоя с приложением, прячет логи вебхуков в общем syslog и делает откат рискованным, когда меняется обработчик одного провайдера.
- Монолитный маршрут часто смешивает cookie-сессии пользователей и машинные ключи — хуже наблюдаемость и шире зона поражения при ошибке маршрутизации.
Нагрузка и где ломается
Зачем свой URL для вебхуков
Команды держат вебхуки на своём сервере по одной главной причине: провайдеры добавляют callback URL в белый список, и стабильный hostname под вашим контролем — hooks.yourcompany.com вместо чужого SaaS-поддомена — проще объяснить службе безопасности и вписать в правила firewall.
Проверка прямо в основном API всё ещё связывает масштабирование и режимы авторизации. Отдельная точка входа на вашем домене держит машинные ключи отдельно от пользовательских сессий.
Компромиссы
Почему VPS или монолитный эндпоинт хрупки
Один эндпоинт на VPS смешивает ритм деплоя с приложением, прячет логи вебхуков в общем syslog и делает откат рискованным, когда меняется обработчик одного провайдера.
Монолитный маршрут часто смешивает cookie-сессии пользователей и машинные ключи — хуже наблюдаемость и шире зона поражения при ошибке маршрутизации.
Как помогает Inquir
Свой ingress, управляемые serverless-обработчики
Вы приносите домен и DNS; Inquir запускает изолированные serverless-функции за маршрутами шлюза. У каждого провайдера — свой обработчик с узкими правами, а не один эндпоинт, где смешаны cookie и секреты вебхуков.
Привяжите свой домен к именованному API через TXT-запись для верификации и CNAME или A-запись; TLS выпускается по запросу. Сырые тела для HMAC, трассы выполнения и передача в пайплайны живут в одном воркспейсе рядом с HTTP API — без SSH на сервер ради логов вебхуков.
Что вы получаете
Возможности обработки вебхуков
Изолированные маршруты вебхуков
Разводите пути провайдеров по небольшим обработчикам с узкими правами.
Трассы выполнения
Смотрите тела (с сокрытием чувствительного) и тайминги, когда провайдер ставит доставку на паузу.
Передача в пайплайны
Быстро подтверждайте, а обработку продолжайте в пайплайне или задаче, когда работа тяжелее окна таймаута.
Что дальше
Как запустить вебхуки на своём домене
Привяжите свой hostname к маршрутам шлюза, проверяйте подписи в serverless-обработчиках, подтверждайте в лимите провайдера и пишите идемпотентно.
Направить DNS на шлюз
Привяжите свой домен и TLS — провайдеры бьют в hooks.yourcompany.com, а не в чужой поддомен.
Проверить и быстро подтвердить
Подпись по сырому телу, ключи идемпотентности, 200 в окне провайдера.
Продолжить в пайплайнах
Медленную обработку передавайте в global.durable.startNew(), чтобы ретраи не дублировали побочные эффекты.
Пример кода
Паттерны провайдеров на вашем домене
Шлюз на вашем hostname передаёт событие в формате AWS API Gateway: body строкой (сырые байты для подписи), заголовки как пришли. Держите по одной функции на провайдера — проверка остаётся компактной и обозримой.
import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); export async function handler(event) { const rawBody = event.body ?? ''; const sig = event.headers['stripe-signature'] ?? ''; let evt; try { evt = stripe.webhooks.constructEvent(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET); } catch (err) { return { statusCode: 400, body: `Webhook Error: ${err.message}` }; } const isNew = await db.upsertWebhookEvent(evt.id, evt.type); if (!isNew) return { statusCode: 200, body: 'duplicate' }; await global.durable.startNew('stripe-fulfillment', undefined, { eventId: evt.id, type: evt.type, data: evt.data.object }); return { statusCode: 200, body: 'accepted' }; }
import { createHmac, timingSafeEqual } from 'node:crypto'; export async function handler(event) { const body = event.body ?? ''; const sigHeader = (event.headers['x-hub-signature-256'] ?? '').replace('sha256=', ''); const expected = createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET).update(body).digest('hex'); if (sigHeader.length !== expected.length || !timingSafeEqual(Buffer.from(sigHeader, 'hex'), Buffer.from(expected, 'hex'))) { return { statusCode: 401, body: 'invalid signature' }; } const eventType = event.headers['x-github-event']; const payload = JSON.parse(body); if (eventType === 'push') { await global.durable.startNew('index-repo', undefined, { repo: payload.repository.full_name, sha: payload.after }); } return { statusCode: 200, body: 'accepted' }; }
import { createHmac, timingSafeEqual } from 'node:crypto'; export async function handler(event) { const body = event.body ?? ''; const timestamp = event.headers['x-slack-request-timestamp'] ?? ''; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) { return { statusCode: 400, body: 'stale request' }; } const sigBase = `v0:${timestamp}:${body}`; const expected = 'v0=' + createHmac('sha256', process.env.SLACK_SIGNING_SECRET).update(sigBase).digest('hex'); const received = event.headers['x-slack-signature'] ?? ''; if (expected.length !== received.length || !timingSafeEqual(Buffer.from(expected), Buffer.from(received))) { return { statusCode: 401, body: 'invalid signature' }; } const params = Object.fromEntries(new URLSearchParams(body)); await global.durable.startNew('slack-command', undefined, { command: params.command, userId: params.user_id, channelId: params.channel_id, text: params.text, responseUrl: params.response_url, }); return { statusCode: 200, body: '' }; }
Когда подходит
Когда свой ingress на Inquir уместен
Когда это уместно
- Нужен стабильный HTTPS на hostname под вашим контролем (свой домен + DNS) и serverless-обработчики на Inquir.
- Хочется развести трафик вебхуков и пользовательские API одного продукта.
Когда лучше выбрать другое
- Вебхуки уходят только в чужую iPaaS без своего рантайма.
Частые вопросы
Частые вопросы
Что делать с медленной работой на следующем шаге?
Подтвердите вебхук быстро, а работу продолжите в serverless-пайплайне или async-задаче — тогда таймауты провайдера не блокируют важные побочные эффекты.
Можно ли закрепить маршруты за тенантами?
Да. Паттерны мультитенантной маршрутизации позволяют сегментировать пути или хосты — подробности на отдельной странице фичи.
Как защититься от replay-атак?
Сочетайте проверку подписи, метки времени провайдера, где они есть, и идемпотентные записи по идентификаторам провайдера.