#Webhook-обработчик событий

🕐 Сложность: medium | Скоупы: crm | Стек: Node.js, Express

#Что делаем

Создаём Express-сервер, который принимает webhook-события от Bitrix24, валидирует их, логирует и выполняет действия в зависимости от типа события. Например, при создании новой сделки сервер автоматически запрашивает её детали через Entity API и записывает в лог. Это основа для построения event-driven интеграций, которые реагируют на изменения в реальном времени.

#Необходимо

  • API-ключ Вайбкод с правами crm
  • Node.js 18+
  • Пакеты: express
  • Публичный URL для приёма webhooks (ngrok для разработки)

#Полный код

javascript
// webhook-handler.js
// Express-сервер для обработки webhook-событий Bitrix24

import express from 'express';

const API_KEY = process.env.VIBE_API_KEY;
const BASE_URL = process.env.VIBE_BASE_URL;
const PORT = process.env.PORT || 3001;

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Получаем сделку через Entity API
async function getDeal(dealId) {
  const response = await fetch(`${BASE_URL}/v1/deals/${dealId}`, {
    headers: { 'X-Api-Key': API_KEY },
  });

  const { data } = await response.json();
  return data;
}

// ──────────────────────────────────────────
// Обработчики событий
// ──────────────────────────────────────────

// Обработка создания новой сделки
async function handleDealAdd(eventData) {
  const dealId = eventData.data?.FIELDS?.ID;
  if (!dealId) {
    console.log('   ⚠️ ID сделки отсутствует в данных события');
    return;
  }

  console.log(`   📋 Загружаем данные сделки #${dealId}...`);

  try {
    const deal = await getDeal(dealId);

    console.log(`   Название: ${deal.title}`);
    console.log(`   Сумма: ${deal.amount} ${deal.currency}`);
    console.log(`   Стадия: ${deal.stageId}`);
    console.log(`   Ответственный: ID ${deal.assignedById}`);

    // Здесь можно добавить дополнительную логику:
    // - отправить уведомление в Slack/Telegram
    // - создать задачу для менеджера
    // - обновить внешнюю систему
  } catch (error) {
    console.error(`   ❌ Ошибка при загрузке сделки: ${error.message}`);
  }
}

// Обработка обновления сделки
async function handleDealUpdate(eventData) {
  const dealId = eventData.data?.FIELDS?.ID;
  if (!dealId) return;

  console.log(`   📋 Сделка #${dealId} обновлена`);

  try {
    const deal = await getDeal(dealId);
    console.log(`   Текущая стадия: ${deal.stageId}`);
    console.log(`   Сумма: ${deal.amount}`);
  } catch (error) {
    console.error(`   ❌ Ошибка: ${error.message}`);
  }
}

// Обработка удаления сделки
function handleDealDelete(eventData) {
  const dealId = eventData.data?.FIELDS?.ID;
  console.log(`   🗑️ Сделка #${dealId} удалена`);
}

// Карта обработчиков по типу события
const EVENT_HANDLERS = {
  ONCRMDEALADD: handleDealAdd,
  ONCRMDEALUPDATE: handleDealUpdate,
  ONCRMDEALDELETE: handleDealDelete,
};

// ──────────────────────────────────────────
// HTTP-эндпоинты
// ──────────────────────────────────────────

// Основной эндпоинт для приёма webhook-событий
app.post('/webhook', async (req, res) => {
  const timestamp = new Date().toLocaleString('ru-RU');

  const eventData = req.body;
  const eventName = eventData.event || 'UNKNOWN';

  console.log(`\n[${timestamp}] 📥 Получено событие: ${eventName}`);
  console.log(`   Данные: ${JSON.stringify(eventData.data?.FIELDS || {})}`);

  // Ищем обработчик для данного события
  const handler = EVENT_HANDLERS[eventName];

  if (handler) {
    try {
      await handler(eventData);
    } catch (error) {
      console.error(`   ❌ Ошибка обработки: ${error.message}`);
    }
  } else {
    console.log(`   ℹ️ Обработчик для события ${eventName} не зарегистрирован`);
  }

  // Bitrix24 ожидает ответ 200, иначе будет повторять запрос
  res.status(200).json({ status: 'ok' });
});

// Проверка работоспособности
app.get('/health', (req, res) => {
  res.json({
    status: 'running',
    uptime: process.uptime(),
    events: Object.keys(EVENT_HANDLERS),
  });
});

// ──────────────────────────────────────────
// Запуск
// ──────────────────────────────────────────

app.listen(PORT, () => {
  console.log('🕐 Webhook-сервер запущен');
  console.log(`   Порт: ${PORT}`);
  console.log(`   Endpoint: POST /webhook`);
  console.log(`   Health: GET /health\n`);

  console.log('💡 Для разработки используйте ngrok:');
  console.log('   npx ngrok http 3001');
});

#Как это работает

  1. Express-сервер запускается и слушает POST-запросы на эндпоинте /webhook.
  2. Bitrix24 отправляет HTTP POST с данными события (тип события, ID сущности и др.) при каждом изменении в CRM.
  3. Сервер определяет тип события (создание, обновление, удаление сделки) и находит соответствующий обработчик.
  4. Обработчик запрашивает актуальные данные сделки через Entity API (GET /v1/deals/:id) для получения полной информации.
  5. Сервер всегда отвечает статусом 200 — иначе Bitrix24 будет повторять отправку события.

Примечание: Регистрация обработчиков событий (event.bind, event.get) выполняется через настройки портала Битрикс24 или прямой REST API. Эти методы не имеют entity-обёртки в Вайбкод, и являются разовыми инфраструктурными операциями.

#Что можно улучшить

  • Добавить очередь событий (Redis/BullMQ) для надёжной обработки при большой нагрузке
  • Реализовать верификацию подписи запроса для защиты от поддельных вызовов
  • Добавить обработчики для других сущностей: контакты, компании, задачи, лиды
  • Сохранять историю полученных событий в базу данных для отладки и аналитики