Привет! Меня зовут Алексей Никитин, руководитель отдела автоматизации направления внедрений в Битрикс24. Тут поговорим о том, что происходит после того, как Битрикс24 отправляет исходящий вебхук? Разбираем POST-запрос, логирование, Flask, Django и базовую защиту endpoint.

Когда говорят про вебхуки в Битрикс24, обычно имеют в виду входящие. Это логично: через них удобно стартовать с REST, дёргать методы портала, выгружать сделки, задачи, контакты и строить внешние скрипты.

В прошлый раз я разобрал эту тему в статье про входящие вебхуки и Python:
Битрикс24 + Python: шесть функций, после которых REST становится проще.

Сегодня рассказываю про исходящие: как Битрикс24 сам отправляет данные на наш сервер, как принять этот запрос и что с ним делать дальше.

Кратко: как работают входящие и исходящие вебхуки в Битрикс24

Входящий вебхук работает так:

  1. Наша программа — например, приложение на сервере или скрипт на компьютере — сама отправляет запрос в Битрикс24 через REST API.

  2. Битрикс24 обрабатывает запрос и возвращает ответ: например, запрошенные данные или информацию об ошибке.

С исходящими всё наоборот: уже сам Битрикс24 при наступлении определённого события отправляет запрос на наш сервер по https. Наша сторона должна быть готова этот запрос принять, сохранить, разобрать и затем провести нужную бизнес-обработку.

Это очень важная мысль, которую полезно зафиксировать в самом начале:

Исходящий вебхук Битрикс24 приходит как HTTP POST-запрос на наш сервер

Дальше начинается самое интересное: что именно пришло? Как это аккуратно залогировать? Чем отличаются два основных сценария использования? Как минимально принять такой запрос на Flask или Django?

Где нужны исходящие вебхуки

Вот несколько примеров разных сценариев, когда исходящие вебхуки полезны:

  • В портале создалась сделка.

  • Обновился контакт.

  • Появилась новая задача.

  • Сработал робот.

  • В бизнес-процессе выполнилось activity, которое должно позвать внешний сервис.

Во всех этих случаях Битрикс24 может сам отправить данные во внешний сервис — то есть в наше приложение.

Так можно не опрашивать портал через REST API с заданным интервалом, проверяя, произошло ли нужное событие. Вместо этого мы создаём endpoint и ждём вызова от Битрикс24: как только событие происходит, портал сам отправляет запрос. Это уменьшает количество лишних запросов к API и позволяет быстрее реагировать на изменения в Битрикс24.

Два вида исходящих вебхуков

На практике я разделяю исходящие вебхуки Битрикс24 на два типа.

1. Исходящие по событиям

Это вариант, когда администратор настраивает реакцию на событие в самом портале. Например, создание задачи, создание или обновление сделки и другие события, которые портал умеет отдавать наружу.

В этом сценарии Битрикс24 сам отправляет POST-запрос на указанный нами обработчик, как только событие произошло. Идея простая: произошло что-то важное в портале, и внешний сервис должен об этом узнать сразу.

Такой вебхук может настроить Администратор портала в разделе:
Разработчикам → Другое → Исходящий вебхук:

Неадмин увидит такое сообщение:

2. Исходящий вебхук через робота или бизнес-процесс

Второй практичный сценарий — робот Исходящий вебхук или activity в бизнес-процессе:

Тут мы сами указываем URL обработчика. После этого, как только робот или блок бизнес-процесса срабатывает, Битрикс24 вызывает наш endpoint. Такой вебхук удобно использовать как один из шагов автоматизации. Например, робот может сразу отправить данные во внешний сервис, если:

  • Сделка дошла до определенной стадии.

  • Лид прошел проверку.

  • Смарт-процесс перешел в нужный этап.

GET-параметры в URL робота: маленькая хитрость, которая часто выручает

Полезный прием: при использовании исходящего вебхука в роботе или бизнес-процессе в URL обработчика можно сразу добавить нужные GET-параметры из сущности, на которой срабатывает робот.

Пример:

https://example.com/bitrix/hook/?deal\\_id={{ID}}&email={{Контакт: E-mail (текст)}}&assigned={{Ответственный}}

Конкретный синтаксис подстановок зависит от того, где именно настраивается робот или блок, но сама идея очень прикладная: вместе с POST-запросом мы заранее передаём себе несколько простых ориентиров.

Что это может быть: ID элемента, EMAIL, телефон, стадия и любая другая короткая информация, которую удобно быстро подхватить в обработчике.

Зачем это нужно: данные могут уйти и в теле запроса, но у GET-параметров есть несколько плюсов:

  • Их легко увидеть в логах.

  • Удобно быстро использовать для маршрутизации.

  • Они помогают быстрее понять, по какому элементу пришел вызов.

  • Иногда позволяют обойтись без лишнего первичного разбора тела запроса.

чувствительные данные в URL передавать не стоит, потому что этот подход может использовать пользователь без необходимых прав.

Что именно приходит от Битрикс24

Популярное неверное ожидание — «строго один стандартный JSON на все случаи». В реальной жизни общий алгоритм такой:

  • Запрос приходит методом POST на наш https-адрес.

  • Вместе с ним мы можем получить query string.

  • В теле запроса будут данные, зависящие от типа источника и сценария.

  • Могут быть важны не только поля тела, но и заголовки, content-type, исходный IP, user-agent.

Поэтому лучше не предполагать заранее, какие именно данные Битрикс24 пришлет в каждом случае. Надёжнее сначала залогировать реальный входящий запрос, посмотреть его структуру и уже после этого писать обработку под фактические данные.

На мой взгляд, это правильный подход. Сначала мы аккуратно дебажим и смотрим, что реально пришло. Потом убеждаемся в структуре данных и только после этого пишем прикладную обработку. При необходимости через входящий вебхук вызываем нужные REST-методы и дальше работаем с REST портала.

Иначе легко заранее предположить структуру запроса и сразу написать под неё обработку, а потом столкнуться с тем, что Битрикс24 прислал другие поля или данные в другом формате.

Минимальный helper для сохранения входящих данных: пишем всё со временем

Для такого стартового сценария достаточно обычной функции. Она получает подготовленные данные запроса, добавляет время получения и дописывает их в лог-файл. Затем эту функцию можно вызывать из обработчика Flask или Django каждый раз, когда Битрикс24 отправляет новый запрос.

import json
from datetime import datetime
from pathlib import Path
from typing import Any


def save_hook_data(nechto: Any, file_path: str = "bitrix_hook_debug.log") -> None:
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_file = Path(file_path)

    try:
        if isinstance(nechto, (dict, list)):
            payload = json.dumps(nechto, ensure_ascii=False, indent=2)
        else:
            payload = str(nechto)
    except Exception:
        payload = repr(nechto)

    with log_file.open(mode="a", encoding="utf-8", errors="ignore") as file:
        file.write(f"[{timestamp}]\n{payload}\n{'-' * 80}\n")

В nechto попадает то значение, которое мы передаём в save_hook_data().

Если проект вырастет, логирование можно вынести в отдельный сервис, helper-класс или полноценную подсистему. Для первого приёма исходящих вебхуков достаточно простой функции — так легче увидеть сам принцип записи входящих данных в лог.

helper выше полезен сам по себе, но ещё лучше, если сохранять не только «что-то одно», а сразу несколько частей входящего запроса: метод, URL, GET-параметры, JSON-тело. Тогда после пары тестовых вызовов от Битрикс24 у вас уже будет фактический материал, по которому легко писать чистую бизнес-логику.

Что логировать в первую очередь

Если вы только подключаете исходящий вебхук, я советую сначала сохранять примерно такой набор:

{
    "method": "...",
    "url": "...",
    "args": {...},
    "form": {...},
    "json": {...},
    "headers": {...},
}

Именно это быстрее всего отвечает на вопрос: «что реально присылает Битрикс24 в этом конкретном сценарии?» После этого мы перестаём гадать и начинаем работать с фактами.

Минимальный пример на Flask

Flask хорош тем, что это микро-фреймворк. Если нужно быстро поднять маленький обработчик и просто начать принимать запросы от Битрикс24, он вполне подходит.

Минимальный пример:

from flask import Flask, jsonify, request

from logger_hook import save_hook_data


app = Flask(__name__)


@app.post("/bitrix/outgoing/")
def bitrix_outgoing() -> tuple:
    data_to_log = {
        "method": request.method,
        "url": request.url,
        "args": request.args.to_dict(flat=False),
        "form": request.form.to_dict(flat=False),
        "json": request.get_json(silent=True),
        "headers": dict(request.headers),
    }

    save_hook_data(data_to_log, file_path="bitrix_outgoing_flask.log")

    # Здесь можно разобрать payload и выполнить нужную бизнес-логику
    # Например: проверить deal_id из query string,
    # затем через входящий вебхук сходить в REST Битрикс24

    return jsonify({"status": "ok"}), 200


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

Этот пример хорош тем, что в нём минимум кода, его можно быстро локально проверить и его удобно начать именно со стадии «принять и посмотреть, что приходит».

Но у Flask есть и естественное ограничение. Поскольку это микро-фреймворк, то если проект начинает расти, нам почти сразу понадобятся дополнительные библиотеки и расширения для моделей и базы, миграций, админки. Поэтому Flask удобен для минимального старта, но не для быстрорастущей интеграции.

Посмотрим, что запишет в лог этот Flask-обработчик на реальном примере. Если вызвать его через исходящий вебхук робота сделки, мы получим примерно такие данные:

{
  "method": "POST",
  "url": "https://***/bitrix/outgoing/?deal\\_id=22424&email=@mail.ru&assigned=user\\_1",
  "args": {
    "deal_id": [
      "22424"
    ],
    "email": [
      "***@mail.ru"
    ],
    "assigned": [
      "user_1"
    ]
  },
  "form": {
    "document_id[0]": [
      "crm"
    ],
    "document_id[1]": [
      "CCrmDocumentDeal"
    ],
    "document_id[2]": [
      "DEAL_22424"
    ],
    "auth[domain]": [
      "***.bitrix24.ru"
    ],
    "auth[client_endpoint]": [
      "https://***.bitrix24.ru/rest/"
    ],
    "auth[server_endpoint]": [
      "https://oauth.bitrix24.tech/rest/"
    ],
    "auth[member_id]": [
      "***"
    ]
  },
  "json": null,
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded",
    "Content-Length": "322",
    "Accept-Encoding": "gzip",
    "X-Protocol": "HTTP/2.0",
    "X-Server-Ip": "***",
    "X-Forwarded-Proto": "https",
    "User-Agent": "Bitrix24 Webhook Engine",
    "Host": "***",
    "X-Forwarded-Protocol": "https",
    "X-Forwarded-For": "***"
  }
}

Для вебхука, привязанного к событию, у нас уже добавится параметр event с названием события, которое вызвало вебхук:

{
  "method": "POST",
  "url": "https://***.ru/bitrix/outgoing/",
  "args": {},
  "form": {
    "event": [
      "ONCRMCOMPANYADD"
    ],
    "event_handler_id": [
      "327"
    ],
    "data[FIELDS][ID]": [
      "11"
    ],
    "ts": [
      "1788110671"
    ],
    "auth[domain]": [
      "***.bitrix24.ru"
    ],
    "auth[client_endpoint]": [
      "https://***.bitrix24.ru/rest/"
    ],
    "auth[server_endpoint]": [
      "https://oauth.bitrix24.tech/rest/"
    ],
    "auth[member_id]": [
      "***"
    ],
    "auth[application_token]": [
      "***"
    ]
  },
  "json": null,
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded",
    "Content-Length": "379",
    "Accept-Encoding": "gzip",
    "X-Protocol": "HTTP/2.0",
    "X-Server-Ip": "***",
    "X-Forwarded-Proto": "https",
    "User-Agent": "Bitrix24 Webhook Engine",
    "Host": "",
    "X-Forwarded-Protocol": "https",
    "X-Forwarded-For": "***"
  }
}

Минимальный пример на Django

С Django история другая. Он тяжелее на входе, но очень многое уже идёт из коробки: ORM, модели, миграции, зрелая структура проекта и готовая база для дальнейшего роста.

Если вы понимаете, что webhook-обработчик может дальше превратиться в нормальный внутренний сервис с журналами, очередями и интерфейсом поддержки, Django часто оказывается практичнее.

Минимальный пример view:

import json

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

from .logger_hook import save_hook_data


@csrf_exempt
def bitrix_outgoing(request):
    if request.method != "POST":
        return JsonResponse({"status": "method not allowed"}, status=405)

    try:
        json_body = json.loads(request.body.decode("utf-8")) if request.body else None
    except json.JSONDecodeError:
        json_body = None

    data_to_log = {
        "method": request.method,
        "url": request.build_absolute_uri(),
        "args": dict(request.GET),
        "form": dict(request.POST),
        "json": json_body,
        "headers": dict(request.headers),
    }

    save_hook_data(data_to_log, file_path="bitrix_outgoing_django.log")

    # Здесь можно:
    # 1. определить тип события;
    # 2. сохранить запрос в модель;
    # 3. поставить задачу в очередь;
    # 4. вызвать входящий вебхук и добрать данные из портала

    return JsonResponse({"status": "ok"})

И маршрут:

from django.urls import path

from .views import bitrix_outgoing


urlpatterns = [
    path("bitrix/outgoing/", bitrix_outgoing, name="bitrix_outgoing"),
]

Почему Django часто удобнее

На мой взгляд, ключевое преимущество Django в таких интеграциях в том, что он быстрее даёт сразу более полный каркас. Например, мы почти сразу можем завести модель для хранения входящих webhook-вызовов, видеть записи в админке и строить повторную обработку и служебные страницы. То есть если задача идет чуть дальше, чем «один endpoint и print в файл», Django часто даёт более предсказуемое развитие проекта.

С Flask всё это тоже можно сделать, но обычно придётся больше собирать руками: дополнительные пакеты, больше инфраструктурных решений, больше обвязки.

Что получается:

  • Flask подходит, чтобы быстро и минималистично принять исходящий вебхук.

  • Django лучше, когда webhook-обработчик должен жить долго и постепенно обрастать прикладной логикой.

Где это реально должно быть развёрнуто

Flask можно запустить на своём компьютере и локально протестировать: написать обработчик, проверить маршруты, протестировать сохранение данных. Но облачный Битрикс24 не сможет обратиться к адресу localhost или 127.0.0.1 на вашем компьютере.

Чтобы Битрикс24 мог отправить исходящий вебхук, обработчику нужен публичный HTTPS-адрес, доступный из интернета. Для этого приложение обычно разворачивают на внешнем сервере или на время разработки публикуют локальный сервер через специальный туннель. На практике нужны эти вещи:

  • Домен или поддомен, доступный из интернета.

  • Рабочий https.

  • Открытый для входящих запросов маршрут.

  • Сервер или хостинг, который запущен постоянно, а не только пока открыт ноутбук.

  • Корректная сетевая доступность без локальных ограничений вида localhost.

То есть адрес вроде: http://127.0.0.1:5000/bitrix/outgoing/ или http://localhost:8000/bitrix/outgoing/ для облачного Битрикс24 бесполезен, потому что портал не сможет обратиться к вашему локальному компьютеру напрямую.

Поэтому в боевом сценарии обработчик обычно разворачивается на VPS, арендованном хостинге с доменом, облачной VM. Ещё один вариант — через временные туннели только для отладки, если нужно быстро посмотреть входящий запрос. То есть сначала мы обеспечиваем внешнюю доступность endpoint для запросов из Битрикса, а уже потом настраиваем исходящий вебхук в Битрикс24.

Архитектурно лучше сразу мыслить так: исходящий вебхук живет не «в ноутбуке», а на публично доступном web-endpoint.

Безопасность приёмника: когда endpoint открыт в сеть

Если обработчик выставлен наружу, это уже не просто «технический URL для Битрикс24», а публичная точка входа в сети. А значит, до неё могут попытаться достучаться не только легитимные запросы портала, но и кто угодно ещё: случайные сканеры, боты, спамеры и просто лишние запросы.

Поэтому приёмник исходящих вебхуков стоит сразу делать не только рабочим, но и защищённым. Вот что стоит сделать.

1. Сделать URL неочевидным

Самая простая первая мера безопасности — не публиковать endpoint по слишком очевидному адресу вроде /webhook/ или /bitrix/. Практичнее использовать более сложный и трудноугадываемый путь. Например:

/bitrix/outgoing/7f3c9a1e5b2d4c8a/

Это не полноценная аутентификация, но такой URL уже снизит количество случайного мусора: сложный адрес уменьшает количество случайных обращений и автоматического сканирования.

Ещё можно добавить отдельный query-параметр-токен:

https://example.com/bitrix/outgoing/?token=7f3c9a1e5b2d4c8a9e21

2. Быстро отбрасывать всё лишнее

Обработчик не должен запускать основную логику для каждого входящего запроса. Сначала полезно выполнить несколько быстрых проверок и сразу отклонить запросы, которые не соответствуют ожидаемому формату.

Что проверять:

  • Метод действительно POST.

  • Путь запроса совпадает с ожидаемым.

  • content-type похож на тот, который вы готовы принимать.

  • Пришли хотя бы минимально ожидаемые параметры.

  • Запрос не слишком большой по размеру.

Если базовая проверка не пройдена, лучше сразу вернуть короткий ответ вроде 403, 404 или 405, не запуская дальнейшую логику. Это важно не только для безопасности, но и просто для здоровья сервера, который не будет тратить ресурсы на заведомо неподходящие запросы — особенно если их приходит много.

3. Проверять заголовки и минимальную сигнатуру запроса

Даже если источник не даёт криптографическую подпись, по которой можно надёжно проверить подлинность запроса, мы всё равно можем быстро отсеивать заведомо неподходящие запросы. Можно проверять наличие ожидаемых заголовков, допустимый content-type, наличие нашего служебного токена или ожидаемых полей в POST и query string.

То есть не нужно принимать в обработку всё подряд. Стоит мыслить примерно так:

  1. Запрос пришёл.

  2. Быстро прошёл короткую серию проверок.

  3. Только после этого попал в основную бизнес-логику.

Такой ранний отсев особенно важен, если дальше у вас идут обращения в базу, вызовы REST Битрикс24, запросы в сторонние системы или тяжёлая внутренняя обработка.

4. Ограничивать размер и частоту запросов

Если endpoint открыт наружу, полезно думать не только про «кто пришёл», но и про «сколько он может прислать». Здесь помогают вполне приземлённые меры:

  • Ограничение размера request body.

  • Rate limit.

  • Таймауты.

  • Ограничение числа одновременных обработок.

  • Вынос тяжёлой логики в очередь.

Почему это важно: даже без изощрённой атаки endpoint можно просто завалить лишними запросами или тяжелыми payload. А если обработчик на каждый входящий вызов начинает долго думать, ходить в базу и дёргать внешние API, он сам становится удобной точкой перегрузки.

Пример: я сталкивался с кейсами, когда пользователи импортировали несколько тысяч сделок, а дальше роботы почти одномоментно при сохранении данных срабатывали и отправляли запросы на сервер.

5. Не делать тяжёлую обработку прямо в запросе

Это уже не только про безопасность, но и про устойчивость. Хорошая схема часто выглядит так:

Приняли запрос → Быстро провалидировали → Сохранили в лог или в базу → Поставили задачу в очередь → Сразу вернули короткий HTTP-ответ

Тогда, даже если запросов станет больше, web-слой не будет слишком долго держать соединение на каждой обработке. А основная логика уже может выполняться отдельно: в worker, cron, background job или очереди задач.

6. Логировать аккуратно

Логирование полезно, но здесь тоже нужна мера. Не стоит без необходимости сохранять чувствительные данные, токены, слишком большие тела запросов и полный набор заголовков каждого запроса на длительный срок.

Лучше логировать вещи, которые помогут понять, что пришло и расследовать проблему. Но при этом логи не должны становиться свалкой секретов и бесконечно разрастаться.

7. По возможности — белые списки и периметр

Если у вас есть контроль над инфраструктурой, можно усиливать защиту уже на уровне периметра. Что можно добавить:

  • Reverse proxy.

  • WAF.

  • Firewall.

  • Rate limiting на nginx.

  • Белые списки IP, если сценарий это допускает.

  • Отдельный поддомен под webhook-обработчики.

Не всегда это можно быстро настроить, особенно в маленьких проектах. Поэтому такие меры оправданы, когда webhook начинает участвовать в важной бизнес-цепочке.

Практический минимальный чек-лист по безопасности

Что я считаю разумным стартом для приёмника исходящих вебхуков:

  1. Сделать сложный и неочевидный URL.

  2. Принимать только ожидаемый POST.

  3. Быстро проверять базовые признаки запроса.

  4. Добавить секретный токен в URL или заголовок.

  5. Ограничить размер и частоту запросов.

  6. Тяжелую обработку уводить из HTTP-обработчика.

  7. Логировать аккуратно и без утечки секретов.

Задача не в том, чтобы построить «неприступную крепость» вокруг простого endpoint. Задача — не обрабатывать впустую весь интернет и не превратить webhook-приёмник в легкую точку перегрузки.

Что делать после того, как запрос принят

Это место, где сходятся исходящие и входящие вебхуки.

Исходящий вебхук часто служит сигналом для запуска дальнейшей обработки. Он сообщает, что в Битрикс24 произошло нужное событие, после чего наш сервер может определить связанную сущность, при необходимости запросить её полные данные через REST и выполнить нужную бизнес-логику.

Например, передать данные во внешнюю систему или сохранить результат обработки. То есть во многих реальных сценариях связка выглядит так:

  1. Битрикс24 отправил исходящий webhook.

  2. Наш сервер принял и залогировал запрос.

  3. Мы поняли, что это за событие и к какой сущности оно относится.

  4. После всего этого — уже через входящий вебхук — пошли в REST и забрали всё, что нужно для полноценной обработки.

Именно поэтому исходящие и входящие вебхуки удобно рассматривать не как конкурентов, а как два дополняющих друг друга механизма.

Практический совет по первому запуску

Если вы только начинаете работать с исходящими вебхуками Битрикс24, не пытайтесь сразу написать идеальную обработку. Надёжнее идти так:

  1. Поднять самый простой endpoint.

  2. Убедиться, что он принимает POST.

  3. Сохранить GET, form, JSON и headers.

  4. Несколько раз вызвать его из Битрикс24 в нужном сценарии.

  5. Посмотреть реальные логи.

  6. Только после этого писать прикладной разбор payload.

Это занимает немного больше дисциплины на старте, но сильно экономит время потом.

Что запомнить

Исходящий вебхук обычно служит началом дальнейшей обработки: сообщает о нужном событии в Битрикс24, а следующие действия уже выполняет ваш обработчик в зависимости от задачи.

Главное:

  • Исходящий вебхук приходит к нам как POST-запрос.

  • Чаще всего полезно разделять два сценария: события и робот/БП.

  • В URL робота удобно передавать несколько GET-параметров вроде ID или EMAIL.

  • Реальную структуру входящих данных лучше не угадывать, а сначала логировать.

  • Для быстрого старта хорошо подходит Flask.

  • Для более взрослого сервиса часто удобнее Django.

  • После приёма webhook мы уже можем использовать входящие вебхуки и REST Битрикс24, чтобы добрать полные данные и выполнить нужную обработку.

Комментарии (1)


  1. SurMaster
    05.10.2026 09:05

    раньше это бесплатно было, сейчас - будь добр оплати отдельную подписку