Интеграция систем через webhook или обратный вызов

Что такое Вебхук?

Вебхук — это механизм асинхронной интеграции, который позволяет одной системе уведомлять другую о произошедшем событии с помощью HTTP-запроса.

Этот технический подход основан на принципе обратного вызова, где сервер (источник события) сам инициирует взаимодействие с клиентом (системой, ожидающей уведомления).

Сравнение подходов к интеграции

В событийно-ориентированной архитектуре часто используются очереди сообщений, но они могут быть избыточными или неуместными, особенно при интеграции с внешними системами, не работающими с брокерами.

Polling (периодический опрос)

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

Webhook (обратный вызов)

Вебхук предлагает альтернативу: клиентская система регистрирует свой API-метод у сервера, говоря: «Когда произойдет событие, вызови мой API-метод». Таким образом, сервер сам выступает в роли инициатора вызова, отправляя уведомление клиенту, когда событие действительно происходит. Это решение позволяет избежать ненужного трафика и оптимизировать взаимодействие.

Теперь давайте рассмотрим это на более живых примерах.

Примеры работы с webhooks

Некоторые сервисы позволяют настроить webhooks прям из интерфейса.

Вебхуки GitHub

Например, популярный сервис GitHub в настройках репозитория позволяет добавить любое количество веб-хуков. Для каждого указывается вызываемый URL, формат полезной нагрузки, а также можно выбрать, при каких событиях, он сработает.

Вебхуки ЮКасса

Другой вариант — это популярный платежный сервис ЮКасса. У ЮКассы есть прекрасная исчерпывающая документация по использованию вебхуков.

Входящие уведомления — Прием оплаты API ЮKassa (yookassa.ru)

Событие в ЮKassa — изменение статуса объекта. Вы можете отслеживать события платежей, возвратов, выплат и сделок. В случае, если применяется базовая аутентификация, вебхуки настраиваются через интерфейс личного кабинета вручную. А в случае, если клиент использует OAuth аутентификацию ЮКассы, она предоставляет API для программной конфигурации вебхуков.

То есть, чтоб сообщить ЮКассе “шли мне уведомление о таком-то событии туда-то“, клиентская сторона вызывает соответствующий метод API, где сообщает свой URL для обратного вызова, и тип событий:

curl https://api.yookassa.ru/v3/webhooks \
  -X POST \
  -H 'Authorization: Bearer <oauth_token>' \
  -H 'Idempotence-Key: <Ключ идемпотентности>' \
  -H 'Content-Type: application/json' \
  -d '{
        "event": "payment.succeeded",
        "url": "https://www.example.com/notification_url"
      }'

Проверка работоспособности вебхука

Если вы хотите проверить, как работает вебхук, но у вас еще нет принимающей стороны и нечего передать в качестве URL для обратного вызова, можно воспользоваться специальным сервисом на сайте Webhook.site — Test, transform and automate Web requests and emails

Здесь нужно сгенерировать уникальный URL, указать его в качестве URL для обратного вызова в том сервисе, с которого вы хотите получать уведомления. И ждать. Когда произойдет событие, на сайте Webhook.site вы увидите в разделе Requests подробности вызова и содержимое тела запроса.

Второй вариант тестирования вебхука “на коленке“ — это создать телеграмм-бота, и указать в качестве URL обратного вызова — URL метода для отправки сообщения в телеграмм-бот, вида https://api.telegram.org/bot{TOKEN}/sendMessage?chat_id={chat_id}&text={txt}, где вместо переменных {TOKEN} и {chat_id} нужно указать токен и идентификатор телеграмм-бота, а также текст сообщения.

Пример применения вебхука

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

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

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

Это долго, и все становятся резко занятыми. Интернет-магазин тем, что постоянно спрашивает. А платежная система тем, что постоянно обрабатывает входящие запросы статуса.

@startuml

title Пример Polling

participant "Интернет-магазин" as Service 
participant "Платежная система" as ExternalService #lightgreen

activate Service
Service -> ExternalService: провести платеж \nHTTP POST /payment
activate ExternalService
ExternalService --> ExternalService: Принять платеж в обработку
ExternalService -> Service: 201 Created 
deactivate ExternalService

Service -> ExternalService: узнать статус \nHTTP GET /payment/status
activate ExternalService
ExternalService -> Service: статус платежа "в работе"
deactivate ExternalService

Service -> ExternalService: узнать статус \nHTTP GET /payment/status
activate ExternalService
ExternalService -> Service: статус платежа "в работе"
deactivate ExternalService

Service -> ExternalService: узнать статус \nHTTP GET /payment/status
activate ExternalService
ExternalService -> Service: статус платежа "в работе"
deactivate ExternalService
...
ExternalService -> ExternalService: перевести деньги со счета на счет
activate ExternalService
deactivate ExternalService
...
Service -> ExternalService: узнать статус \nHTTP GET /payment/status
activate ExternalService
ExternalService -> Service: статус платежа "выполнен"
deactivate ExternalService

Service --> Service: Обновить статус платежа в БД
deactivate Service

@enduml

Вместо этого можно просто дожидаться уведомления от платежной системы о том, что платеж прошел. В этом случае все занимаются своим делом и никто никого не бомбит лишним трафиком. Когда платеж будет проведен (либо отклонен), платежная система вызовет REST API метод интернет-магазина, и тот обновит статус заказа.

@startuml
title Пример Webhook 

participant "Интернет-магазин" as Service 
participant "Платежная система" as ExternalService #lightgreen

activate Service
Service -> ExternalService: провести платеж \nHTTP POST /payment
activate ExternalService
ExternalService --> ExternalService: Принять платеж в обработку
ExternalService -> Service: 201 Created 
deactivate ExternalService
deactivate Service

...

ExternalService --> ExternalService: Перевести деньги со счета на счет
activate ExternalService
ExternalService -> Service: вызов вебхука\nPOST /payment/status
activate Service
Service --> Service: Обновить статус платежа в БД
Service --> ExternalService: 200 OK
deactivate Service
deactivate ExternalService

@enduml

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