Как стать автором
Обновить
522.57
OTUS
Цифровые навыки от ведущих экспертов

Как обрабатывать сделки Bitrix24 с помощью Flask и Node.js

Уровень сложностиПростой
Время на прочтение5 мин
Количество просмотров1.1K

Привет, Хабр!

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

Теги:
Хабы:
+1
Комментарии2

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS