Привет, Хабр!
Сегодня мы будем строить сервис для автоматической обработки сделок в 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. Узнать подробнее