[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-recipes\u002Fwebhook-handler":3,"docs-tabs-recipes\u002Fwebhook-handler":6},{"content":4,"lastmod":5},"# Webhook-обработчик событий\n\n🕐 **Сложность:** medium | **Скоупы:** crm | **Стек:** Node.js, Express\n\n## Что делаем\nСоздаём Express-сервер, который принимает webhook-события от Bitrix24, валидирует их, логирует и выполняет действия в зависимости от типа события. Например, при создании новой сделки сервер автоматически запрашивает её детали через Entity API и записывает в лог. Это основа для построения event-driven интеграций, которые реагируют на изменения в реальном времени.\n\n> **Приложение на Black Hole-сервере?** Тогда публичный URL и ngrok не нужны — есть управляемый путь доставки событий через туннель. Платформа сама регистрирует обработчик в Битрикс24 и доставляет события в приложение с повторами и пробуждением спящего сервера. Подробнее: [Подписки на события портала](\u002Fdocs\u002Finfra\u002Fevent-subscriptions). Рецепт ниже — для приложений вне Black Hole, у которых есть собственный публичный адрес.\n\n## Необходимо\n- API-ключ Вайбкод с правами `crm`\n- Node.js 18+\n- Пакеты: `express`\n- Публичный URL для приёма webhooks (ngrok для разработки)\n\n## Полный код\n```javascript\n\u002F\u002F webhook-handler.js\n\u002F\u002F Express-сервер для обработки webhook-событий Bitrix24\n\nimport express from 'express';\n\nconst API_KEY = process.env.VIBE_API_KEY;\nconst BASE_URL = process.env.VIBE_BASE_URL;\nconst PORT = process.env.PORT || 3001;\n\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\n\u002F\u002F Получаем сделку через Entity API\nasync function getDeal(dealId) {\n  const response = await fetch(`${BASE_URL}\u002Fv1\u002Fdeals\u002F${dealId}`, {\n    headers: { 'X-Api-Key': API_KEY },\n  });\n\n  const { data } = await response.json();\n  return data;\n}\n\n\u002F\u002F ──────────────────────────────────────────\n\u002F\u002F Обработчики событий\n\u002F\u002F ──────────────────────────────────────────\n\n\u002F\u002F Обработка создания новой сделки\nasync function handleDealAdd(eventData) {\n  const dealId = eventData.data?.FIELDS?.ID;\n  if (!dealId) {\n    console.log('   ⚠️ ID сделки отсутствует в данных события');\n    return;\n  }\n\n  console.log(`   📋 Загружаем данные сделки #${dealId}...`);\n\n  try {\n    const deal = await getDeal(dealId);\n\n    console.log(`   Название: ${deal.title}`);\n    console.log(`   Сумма: ${deal.amount} ${deal.currency}`);\n    console.log(`   Стадия: ${deal.stageId}`);\n    console.log(`   Ответственный: ID ${deal.assignedById}`);\n\n    \u002F\u002F Здесь можно добавить дополнительную логику:\n    \u002F\u002F - отправить уведомление в Slack\u002FTelegram\n    \u002F\u002F - создать задачу для менеджера\n    \u002F\u002F - обновить внешнюю систему\n  } catch (error) {\n    console.error(`   ❌ Ошибка при загрузке сделки: ${error.message}`);\n  }\n}\n\n\u002F\u002F Обработка обновления сделки\nasync function handleDealUpdate(eventData) {\n  const dealId = eventData.data?.FIELDS?.ID;\n  if (!dealId) return;\n\n  console.log(`   📋 Сделка #${dealId} обновлена`);\n\n  try {\n    const deal = await getDeal(dealId);\n    console.log(`   Текущая стадия: ${deal.stageId}`);\n    console.log(`   Сумма: ${deal.amount}`);\n  } catch (error) {\n    console.error(`   ❌ Ошибка: ${error.message}`);\n  }\n}\n\n\u002F\u002F Обработка удаления сделки\nfunction handleDealDelete(eventData) {\n  const dealId = eventData.data?.FIELDS?.ID;\n  console.log(`   🗑️ Сделка #${dealId} удалена`);\n}\n\n\u002F\u002F Карта обработчиков по типу события\nconst EVENT_HANDLERS = {\n  ONCRMDEALADD: handleDealAdd,\n  ONCRMDEALUPDATE: handleDealUpdate,\n  ONCRMDEALDELETE: handleDealDelete,\n};\n\n\u002F\u002F ──────────────────────────────────────────\n\u002F\u002F HTTP-эндпоинты\n\u002F\u002F ──────────────────────────────────────────\n\n\u002F\u002F Основной эндпоинт для приёма webhook-событий\napp.post('\u002Fwebhook', async (req, res) => {\n  const timestamp = new Date().toLocaleString('ru-RU');\n\n  const eventData = req.body;\n  const eventName = eventData.event || 'UNKNOWN';\n\n  console.log(`\\n[${timestamp}] 📥 Получено событие: ${eventName}`);\n  console.log(`   Данные: ${JSON.stringify(eventData.data?.FIELDS || {})}`);\n\n  \u002F\u002F Ищем обработчик для данного события\n  const handler = EVENT_HANDLERS[eventName];\n\n  if (handler) {\n    try {\n      await handler(eventData);\n    } catch (error) {\n      console.error(`   ❌ Ошибка обработки: ${error.message}`);\n    }\n  } else {\n    console.log(`   ℹ️ Обработчик для события ${eventName} не зарегистрирован`);\n  }\n\n  \u002F\u002F Bitrix24 ожидает ответ 200, иначе будет повторять запрос\n  res.status(200).json({ status: 'ok' });\n});\n\n\u002F\u002F Проверка работоспособности\napp.get('\u002Fhealth', (req, res) => {\n  res.json({\n    status: 'running',\n    uptime: process.uptime(),\n    events: Object.keys(EVENT_HANDLERS),\n  });\n});\n\n\u002F\u002F ──────────────────────────────────────────\n\u002F\u002F Запуск\n\u002F\u002F ──────────────────────────────────────────\n\napp.listen(PORT, () => {\n  console.log('🕐 Webhook-сервер запущен');\n  console.log(`   Порт: ${PORT}`);\n  console.log(`   Endpoint: POST \u002Fwebhook`);\n  console.log(`   Health: GET \u002Fhealth\\n`);\n\n  console.log('💡 Для разработки используйте ngrok:');\n  console.log('   npx ngrok http 3001');\n});\n```\n\n## Как это работает\n1. Express-сервер запускается и слушает POST-запросы на эндпоинте `\u002Fwebhook`.\n2. Bitrix24 отправляет HTTP POST с данными события (тип события, ID сущности и др.) при каждом изменении в CRM.\n3. Сервер определяет тип события (создание, обновление, удаление сделки) и находит соответствующий обработчик.\n4. Обработчик запрашивает актуальные данные сделки через Entity API (`GET \u002Fv1\u002Fdeals\u002F:id`) для получения полной информации.\n5. Сервер всегда отвечает статусом 200 — иначе Bitrix24 будет повторять отправку события.\n\n> **Примечание:** Регистрация обработчиков событий (`event.bind`, `event.get`) выполняется через настройки портала Битрикс24 или прямой REST API. Эти методы не имеют entity-обёртки в Вайбкод, и являются разовыми инфраструктурными операциями. Для приложения на Black Hole-сервере регистрация управляемая — см. [Подписки на события портала](\u002Fdocs\u002Finfra\u002Fevent-subscriptions).\n\n## Что можно улучшить\n- Добавить очередь событий (Redis\u002FBullMQ) для надёжной обработки при большой нагрузке\n- Реализовать верификацию подписи запроса для защиты от поддельных вызовов\n- Добавить обработчики для других сущностей: контакты, компании, задачи, лиды\n- Сохранять историю полученных событий в базу данных для отладки и аналитики\n","2026-07-07",{}]