Привет, Хабр!
Сегодня мы будем строить сервис для автоматической обработки сделок в Bitrix24, используя Flask и Node.js. Этот сервис будет:
Принимать вебхуки ONCRMDEALUPDATE.
Валидировать подписи и структуру запроса.
Извлекать
deal_id.Получать данные о сделке.
Проверять сумму сделки и другие условия.
Обновлять стадию сделки.
Создавать задачу для менеджера.
Защищаться от рекурсии и ошибок.
Но для начала... как Bitrix формирует вебхук? Когда срабатывает событие ONCRMDEALUPDATE, Bitrix шлёт payload, где все данные сделки, включая deal_id, находятся внутри структуры data → FIELDS → ID. Это важно, потому что если просто попытаться получить data['ID'], то в случае отсутствия ключа ты получишь ошибку.
Чтобы этого избежать, всегда используем .get(), который безопасно возвращает None в случае отсутствия нужного ключа, а не выкидывает исключение. Так мы можем обработать любые данные и избежать краха логики, если структура данных по каким‑то причинам изменится или не будет полной.
Flask-сервер для обработки вебхуков
Установим зависимости:
pip install flask requests redis
Код самого сервера:
from flask import Flask, request, jsonify import hmac, hashlib, logging, requests import redis app = Flask(__name__) logging.basicConfig(level=logging.INFO) WEBHOOK_KEY = 'https://your.bitrix24.ru/rest/1/abc123xyz456789/' SECRET = b'your_super_secret_key' # если используешь подпись REDIS = redis.Redis(host='localhost', port=6379, db=0) TTL_SECONDS = 15 # анти-петля TTL @app.route('/webhook', methods=['POST']) def webhook(): raw = request.data data = request.json sig = request.headers.get('X-Bitrix-Signature', '') # Валидация подписи (если настроена) if sig: expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(sig, expected): logging.warning("Invalid signature") return jsonify({'error': 'bad signature'}), 403 deal_id = data.get('data', {}).get('FIELDS', {}).get('ID') if not deal_id: logging.error("No deal ID in payload") return jsonify({'error': 'no deal ID'}), 400 if REDIS.get(deal_id): logging.info(f"Skip repeated deal ID: {deal_id}") return jsonify({'status': 'skipped'}), 200 REDIS.set(deal_id, 1, ex=TTL_SECONDS) try: process_deal(deal_id) except Exception as e: logging.exception(f"Error during deal processing: {e}") return jsonify({'error': 'internal'}), 500 return jsonify({'status': 'ok'})
Проверяем подпись через HMAC для безопасности и извлекаем deal_id безопасно с помощью .get(). Redis тут для предотвращения повторной обработки одной и той же сделки в течение короткого времени, чтобы избежать зацикливания. Вся логика обработки сделок обёрнута в try/except.
Бизнес-логика обработки сделки
Теперь реализуем обработку самой сделки:
def process_deal(deal_id): r = requests.post(WEBHOOK_KEY + 'crm.deal.get', json={'id': deal_id}) if r.status_code != 200: logging.warning(f"Bitrix GET failed: {r.status_code}") return deal = r.json().get('result') if not deal: logging.warning(f"Deal {deal_id} not found in response") return amount = float(deal.get('OPPORTUNITY', 0)) manager = deal.get('ASSIGNED_BY_ID') current_stage = deal.get('STAGE_ID') logging.info(f"Deal #{deal_id}: amount={amount}, stage={current_stage}") if amount > 100000: update_stage(deal_id) create_task(deal_id, manager, amount)
Отправляем запрос к API Bitrix24 с crm.deal.get, чтобы получить данные о сделке. Статус‑код проверяется для корректности ответа, и если сделка не найдена, логируем это. Также конвертируем значение суммы сделки в float, т.к оно может быть пустым или строкой. Если сумма превышает 100 000, происходит обновление стадии и создание задачи для менеджера.
Смена стадии и создание задачи
Код для смены стадии сделки:
def update_stage(deal_id): res = requests.post(WEBHOOK_KEY + 'crm.deal.update', json={ 'id': deal_id, 'fields': { 'STAGE_ID': 'NEW_STAGE_CODE' } }) if res.status_code != 200: logging.warning(f"Failed to update stage for deal {deal_id}") else: logging.info(f"Stage updated for deal {deal_id}")
Для изменения стадии сделки используем API Bitrix24. Если запрос не удался, это логируется.
Код для создания задачи:
def create_task(deal_id, manager_id, amount): res = requests.post(WEBHOOK_KEY + 'tasks.task.add', json={ 'fields': { 'TITLE': f'Сделка на {amount}', 'DESCRIPTION': f'Сделка #{deal_id} превысила 100 000. Проверь.', 'RESPONSIBLE_ID': manager_id } }) if res.status_code != 200: logging.error(f"Ошибка создания задачи по сделке {deal_id}") else: logging.info(f"Задача создана по сделке {deal_id}")
Создаем задачу для менеджера, если сделка превышает определённую сумму. В запросе указывается описание задачи и ID ответственного менеджера.
Node.js-версия
Установим зависимости:
npm install express body-parser axios redis
Код сервера на Node.js:
const express = require('express'); const crypto = require('crypto'); const axios = require('axios'); const Redis = require('ioredis'); const app = express(); app.use(express.json()); const redis = new Redis(); const BITRIX_WEBHOOK = 'https://your.bitrix24.ru/rest/1/abc123xyz456789/'; const SECRET = 'your_super_secret_key'; function isValidSig(body, signature) { const expected = crypto.createHmac('sha256', SECRET).update(JSON.stringify(body)).digest('hex'); return signature === expected; } app.post('/webhook', async (req, res) => { const sig = req.headers['x-bitrix-signature'] || ''; const dealId = req.body?.data?.FIELDS?.ID; if (sig && !isValidSig(req.body, sig)) { return res.status(403).send('Invalid signature'); } if (!dealId) return res.status(400).send('Missing deal ID'); const isDuplicate = await redis.get(dealId); if (isDuplicate) return res.status(200).send('Skipped'); await redis.set(dealId, 1, 'EX', 15); try { const dealRes = await axios.post(BITRIX_WEBHOOK + 'crm.deal.get', { id: dealId }); const deal = dealRes.data.result; const amount = parseFloat(deal.OPPORTUNITY); const manager = deal.ASSIGNED_BY_ID; if (amount > 100000) { await axios.post(BITRIX_WEBHOOK + 'crm.deal.update', { id: dealId, fields: { STAGE_ID: 'NEW_STAGE_CODE' } }); await axios.post(BITRIX_WEBHOOK + 'tasks.task.add', { fields: { TITLE: `Сделка на ${amount}`, RESPONSIBLE_ID: manager, DESCRIPTION: `Проверь сделку #${dealId}` } }); } res.json({ status: 'ok' }); } catch (err) { console.error('Ошибка обработки:', err.message); res.status(500).send('Server error'); } }); app.listen(3000, () => console.log('Слушаем порт 3000'));
Код выполняет ту же логику, что и на Flask, но с Express. Проверяем подпись запроса, используем Redis для предотвращения повторной обработки сделок, а также работаем с API Bitrix24, чтобы получать информацию о сделке и обновлять её, если сумма превышает 100 000.
А какой у вас опыт работы с вебхуками и автоматизацией в Bitrix24? Делитесь в комментариях.
Статья подготовлена для будущих студентов онлайн‑курса «Интегратор Битрикс24». Хорошая новость: в рамках этого курса студенты получат поддержку карьерного центра Otus. Узнать подробнее
