The post has been translated automatically. Original language: Russian
The Internet has long ceased to be a space in which information is transmitted only by people.
While a user pays for an order, posts a message, or changes settings in the app, dozens of automated systems exchange data without their direct involvement. The payment service notifies the online store of a successful operation, the analytics system records the event, the CRM updates the customer's card, the delivery service receives a new order, and the corporate messenger sends a notification to the responsible employee.
The scale of such automated interaction is constantly growing. According to the Imperva report, in 2024, automated bots generated 51% of web traffic. Of course, not all of this traffic is associated with useful business processes: the statistics include search robots, AI crawlers, and malicious automation. Nevertheless, the indicator illustrates the general trend well: a significant part of modern Internet activity is created by programs that interact with other programs.
Webhooks are one of the most common ways of such interaction.
What is a webhook?
A webhook is an automatic HTTP notification that one service sends to another when a certain event occurs.
For example:
- The payment system informs the online store that the payment has been completed;
- CRM notifies the internal system when a new customer appears;
- The version control system reports a new commit.;
- The delivery service transmits the updated status of the shipment;
- The monitoring service notifies the team that the server is unavailable.
The main advantage of webhooks is their speed. The recipient does not need to constantly contact another service and ask if any change has occurred. The sender transmits the notification itself immediately after the event.
At first glance, the mechanism looks simple: an event occurred → the service sent the request → the recipient processed it.
But in a real infrastructure, there are many components between sending a request and successfully processing it: ISPs, routers, load balancers, proxy servers, security systems, web servers, applications, and databases.
Any of these components may be temporarily unavailable or may not work as expected.
Why are webhooks sometimes sent repeatedly?
Let's imagine that a payment service has sent an online store a notification of a successful payment.
The store received the request, created the order, and sent a 200 OK response confirming successful processing. However, due to a short-term network error, the response did not reach the payment service.
The operation was completed successfully for the online store: the order already exists. But from the sender's point of view, the result is unknown. He has not received confirmation and cannot determine whether the request has been processed.
In such a situation, many systems retry the delivery. This is a reasonable behavior: it is better to repeat the notification than to permanently lose an important event.
The problem occurs on the recipient's side. If it processes the repeated request as a new event, the business operation can be performed again.
The consequences depend on the purpose of the webhook:
- a second customer or a second order will appear in CRM;
- the user will receive the same notification twice;
- the system will re-award bonuses;
- The warehouse will reserve the product twice;
- The application will restart an expensive task.;
- The external service will receive another request.;
- The system can initiate a repeat financial transaction.
Even if an error does not lead to an immediate loss of money, it creates additional costs: employees have to look for the cause of discrepancies, delete duplicates, correct data, and explain the situation to customers.
Thus, the mechanism of repeated attempts simultaneously increases the reliability of delivery and creates a new risk — the repeated execution of an already completed operation.
Message delivery and operation execution are not the same thing.
In distributed systems, it is almost impossible to guarantee that a message will always be delivered exactly once.
The sender can guarantee that they will attempt delivery. He can repeat it until he receives confirmation. But in certain situations, he is unable to determine whether the previous attempt was really unsuccessful or only seemed unsuccessful due to a lost answer.
Therefore, reliable integration must separate the two concepts.:
- The request can be delivered multiple times.
- The business operation associated with it should be performed only once.
This property is called idempotence.
Idempotent processing means that repeated execution of the same operation does not change the final result.
For example, the command "set the order status to "paid"" is naturally close to idempotent. If you complete it again, the order will still remain paid.
And the "create new order" command is not idempotent. Two identical requests without additional protection can create two different orders.
How does the system distinguish a repeat delivery from a new event?
Comparing the query contents is not enough.
Two different orders may have the same price, currency, and set of items. Two messages can contain the same text. Two operations can occur in one second and have almost identical parameters.
The same content does not mean that we are facing a repeat delivery of a single event.
To solve this problem, idempotency keys are used — idempotence keys.
An idempotence key is a unique identifier for a specific logical operation. The sender transmits it along with the request, and the recipient saves and verifies it before performing actions.
The general principle is as follows:
- The system receives a webhook.
- The idempotence key is extracted from the request.
- The recipient checks whether this key has been processed before.
- If the key is new, the system atomically registers it and performs the operation.
- If the key already exists, no repeat business operation is performed.
This is the approach used, in particular, in the Stripe API: the client transmits a unique key, by which the server recognizes repeated attempts to perform a single operation.
It is important to understand that a random identifier alone does not make the system idempotent. The recipient must actually save the key and ensure its uniqueness.
The check should also be atomic. If two identical requests arrive at the same time, both should not have time to determine that the key has not yet been registered and perform the operation in parallel.
Therefore, protection is usually provided not only by a software condition, but also by a uniqueness constraint at the database level.
Why should the key be saved on repeated attempts?
The idempotence key does not identify an individual sending attempt, but the logical operation itself.
Therefore, if the delivery is repeated due to a timeout or a network error, the key must remain the same.
For example:
- The first delivery attempt is the ABC123 key;
- automatic retry — key ABC123;
- manual repeat of the same delivery — key ABC123;
The recipient sees that all requests relate to a single operation, and performs the action only on the first request.
If a new identifier is generated each time, the recipient will not be able to distinguish a repeat delivery from a new event. The idempotence mechanism will lose its meaning.
In this case, the intentional re-execution of an old event can be considered as a new operation. For example, if a user deliberately creates a new request based on a previously received webhook, they may need a new key.
The difference is determined not by the matching of the query contents, but by the meaning of the action being performed.
It's not just the sender who needs repeated attempts.
Not every service guarantees the re-delivery of webhooks.
Some platforms automatically repeat requests in case of an error, others allow you to restart the delivery manually, and still others do nothing more after the first unsuccessful attempt. For example, GitHub does not automatically repeat failed deliveries, but allows you to manually re-deliver webhooks for the last three days.
For a business, this means that the temporary unavailability of the receiving server may lead to the final loss of the event.
An additional intermediate delivery layer allows you to solve this problem. It accepts a webhook from the source service, stores it, and then delivers it to the destination system. If the recipient is temporarily unavailable, the delivery will be repeated later.
This is exactly the approach implemented in Adal is a webhook delivery and observability platform.
Adal can accept the request, save the information about it and repeat the delivery in case of a temporary error. Thus, if the original service has not implemented its own retry mechanism, Adal performs this task at the delivery infrastructure level.
But repeated attempts bring us back to the risk of duplication. Therefore, when developing Adal, we provided for the possibility to add the header to the sent requests: X-Adal-Idempotency
Its value remains unchanged for automatic and manual repeated attempts of the same delivery. When intentionally creating a new request based on an old event, a new key is generated.
The function is enabled separately for each delivery destination. This is fundamentally important: Adal does not change queries covertly and does not make a decision instead of the user. If the receiving system requires you to save the original request completely, you can disable the additional header. If protection against re-processing is necessary, it can be enabled explicitly.
At the same time, responsibility remains distributed among the integration participants.:
- Adal transmits a stable identifier for a single operation;
- the receiving system saves it;
- the database ensures uniqueness;
- the business logic does not perform side actions repeatedly.
Only the combined work of these components makes re-delivery safe.
Reliability is not the absence of repetition.
Re-delivery of a webhook does not always indicate an error in the system.
On the contrary, it is often the result of a mechanism that tries not to lose data in an unstable network. The sender repeats the request because he has not received reliable confirmation of the result.
Therefore, a reliable architecture should not assume that each webhook will arrive exactly once.
A safer assumption sounds different.: The webhook can be delivered repeatedly, but the business operation must be performed once.
Idempotence keys allow you to explicitly identify an operation and distinguish a new command from a technical repeat. Repeated attempts, in turn, reduce the chance of losing the event.
These mechanisms do not replace each other. They solve two opposite but interrelated tasks.:
- repeated attempts protect against loss of requests.;
- idempotence protects against repeated actions.
Together, they allow you to build integrations that remain predictable even when the network, servers, or external services are not working perfectly.
For modern digital systems, this is not an additional optimization, but one of the basic principles of reliable automation.
Интернет давно перестал быть пространством, в котором информацию передают только люди.
Пока пользователь оплачивает заказ, публикует сообщение или меняет настройки в приложении, десятки автоматизированных систем обмениваются данными без его непосредственного участия. Платёжный сервис уведомляет интернет-магазин об успешной операции, система аналитики фиксирует событие, CRM обновляет карточку клиента, служба доставки получает новый заказ, а корпоративный мессенджер отправляет уведомление ответственному сотруднику.
Масштаб такого автоматизированного взаимодействия постоянно растёт. По данным отчёта Imperva, в 2024 году автоматизированные боты сформировали 51% веб-трафика. Разумеется, далеко не весь этот трафик связан с полезными бизнес-процессами: в статистику входят поисковые роботы, AI-краулеры и вредоносная автоматизация. Тем не менее показатель хорошо иллюстрирует общую тенденцию: значительная часть современной интернет-активности создаётся программами, которые взаимодействуют с другими программами.
Одним из распространённых способов такого взаимодействия являются вебхуки.
Что такое вебхук
Вебхук — это автоматическое HTTP-уведомление, которое один сервис отправляет другому при наступлении определённого события.
Например:
- платёжная система сообщает интернет-магазину, что платёж завершён;
- CRM уведомляет внутреннюю систему о появлении нового клиента;
- система контроля версий сообщает о новом коммите;
- служба доставки передаёт обновлённый статус отправления;
- сервис мониторинга уведомляет команду о недоступности сервера.
Главное преимущество вебхуков заключается в их оперативности. Получателю не нужно постоянно обращаться к другому сервису и спрашивать, произошло ли какое-либо изменение. Отправитель сам передаёт уведомление сразу после события.
На первый взгляд механизм выглядит просто: произошло событие → сервис отправил запрос → получатель обработал его.
Но в реальной инфраструктуре между отправкой запроса и его успешной обработкой находится множество компонентов: интернет-провайдеры, маршрутизаторы, балансировщики нагрузки, прокси-серверы, системы защиты, веб-серверы, приложения и базы данных.
Любой из этих компонентов может временно оказаться недоступным или сработать не так, как ожидалось.
Почему вебхуки иногда отправляются повторно
Представим, что платёжный сервис отправил интернет-магазину уведомление об успешной оплате.
Магазин получил запрос, создал заказ и отправил ответ 200 OK, подтверждающий успешную обработку. Однако из-за кратковременной сетевой ошибки ответ не дошёл обратно до платёжного сервиса.
Для интернет-магазина операция завершилась успешно: заказ уже существует. Но с точки зрения отправителя результат неизвестен. Он не получил подтверждения и не может определить, был ли запрос обработан.
В такой ситуации многие системы выполняют повторную попытку доставки. Это разумное поведение: лучше повторить уведомление, чем окончательно потерять важное событие.
Проблема возникает на стороне получателя. Если он обработает повторный запрос как новое событие, бизнес-операция может выполниться ещё раз.
Последствия зависят от назначения вебхука:
- в CRM появится второй клиент или второй заказ;
- пользователь дважды получит одно уведомление;
- система повторно начислит бонусы;
- склад дважды зарезервирует товар;
- приложение повторно запустит дорогостоящую задачу;
- внешний сервис получит ещё один запрос;
- система может инициировать повторную финансовую операцию.
Даже если ошибка не приводит к непосредственной потере денег, она создаёт дополнительные расходы: сотрудникам приходится искать причину расхождений, удалять дубликаты, исправлять данные и объяснять ситуацию клиентам.
Таким образом, механизм повторных попыток одновременно повышает надёжность доставки и создаёт новый риск — повторное выполнение уже завершённой операции.
Доставка сообщения и выполнение операции — не одно и то же
В распределённых системах практически невозможно гарантировать, что сообщение всегда будет доставлено ровно один раз.
Отправитель может гарантировать, что он предпримет попытку доставки. Он может повторять её до получения подтверждения. Но в определённых ситуациях он не способен установить, была ли предыдущая попытка действительно неуспешной или только показалась неуспешной из-за потерянного ответа.
Поэтому надёжная интеграция должна разделять два понятия:
- Запрос может быть доставлен несколько раз.
- Связанная с ним бизнес-операция должна быть выполнена только один раз.
Это свойство называется идемпотентностью.
Идемпотентная обработка означает, что повторное выполнение одной и той же операции не изменяет итоговый результат.
Например, команда «установить заказу статус “оплачен”» естественным образом близка к идемпотентной. Если выполнить её повторно, заказ по-прежнему останется оплаченным.
А команда «создать новый заказ» не является идемпотентной. Два одинаковых запроса без дополнительной защиты могут создать два разных заказа.
Как система отличает повторную доставку от нового события
Сравнивать содержимое запросов недостаточно.
Два разных заказа могут иметь одинаковую стоимость, валюту и набор товаров. Два сообщения могут содержать одинаковый текст. Две операции могут произойти в одну секунду и иметь практически одинаковые параметры.
Одинаковое содержимое ещё не означает, что перед нами повторная доставка одного события.
Для решения этой проблемы используются idempotency keys — ключи идемпотентности.
Ключ идемпотентности — это уникальный идентификатор конкретной логической операции. Отправитель передаёт его вместе с запросом, а получатель сохраняет и проверяет перед выполнением действий.
Общий принцип выглядит следующим образом:
- Система получает вебхук.
- Из запроса извлекается ключ идемпотентности.
- Получатель проверяет, обрабатывался ли этот ключ ранее.
- Если ключ новый, система атомарно регистрирует его и выполняет операцию.
- Если ключ уже существует, повторная бизнес-операция не выполняется.
Именно такой подход используется, в частности, в API Stripe: клиент передаёт уникальный ключ, по которому сервер распознаёт повторные попытки выполнения одной операции.
Важно понимать, что сам по себе случайный идентификатор ещё не делает систему идемпотентной. Получатель должен действительно сохранять ключ и обеспечивать его уникальность.
Проверка также должна быть атомарной. Если два одинаковых запроса поступят одновременно, оба не должны успеть определить, что ключ ещё не зарегистрирован, и выполнить операцию параллельно.
Поэтому защита обычно обеспечивается не только программным условием, но и ограничением уникальности на уровне базы данных.
Почему ключ должен сохраняться при повторных попытках
Ключ идемпотентности идентифицирует не отдельную попытку отправки, а саму логическую операцию.
Следовательно, если доставка повторяется из-за тайм-аута или сетевой ошибки, ключ должен оставаться прежним.
Например:
- первая попытка доставки — ключ ABC123;
- автоматическая повторная попытка — ключ ABC123;
- ручной повтор той же доставки — ключ ABC123;
Получатель видит, что все запросы относятся к одной операции, и выполняет действие только при первом обращении.
Если при каждой попытке генерировать новый идентификатор, получатель не сможет отличить повторную доставку от нового события. Механизм идемпотентности потеряет смысл.
При этом намеренное повторное выполнение старого события может рассматриваться как новая операция. Например, если пользователь сознательно создаёт новый запрос на основе ранее полученного вебхука, для него может потребоваться новый ключ.
Разница определяется не совпадением содержимого запросов, а смыслом выполняемого действия.
Повторные попытки нужны не только отправителю
Не каждый сервис гарантирует повторную доставку вебхуков.
Некоторые платформы автоматически повторяют запросы при ошибке, другие позволяют перезапустить доставку вручную, а третьи после первой неудачной попытки больше ничего не делают. Например, GitHub не повторяет неудачные доставки автоматически, но позволяет вручную повторно доставить вебхуки за последние три дня.
Для бизнеса это означает, что временная недоступность принимающего сервера может привести к окончательной потере события.
Дополнительный промежуточный слой доставки позволяет решить эту проблему. Он принимает вебхук от исходного сервиса, сохраняет его и затем доставляет в конечную систему. Если получатель временно недоступен, доставка повторяется позднее.
Именно такой подход реализован в Adal — платформе доставки и наблюдаемости вебхуков.
Adal может принять запрос, сохранить сведения о нём и повторять доставку в случае временной ошибки. Таким образом, если исходный сервис не реализовал собственный механизм повторных попыток, Adal выполняет эту задачу на уровне инфраструктуры доставки.
Но повторные попытки снова возвращают нас к риску дублирования. Поэтому при разработке Adal мы предусмотрели возможность добавлять к отправляемым запросам заголовок: X-Adal-Idempotency
Его значение остаётся неизменным для автоматических и ручных повторных попыток одной доставки. При намеренном создании нового запроса на основе старого события формируется новый ключ.
Функция включается отдельно для каждого направления доставки. Это принципиально важно: Adal не изменяет запросы скрытно и не принимает решение вместо пользователя. Если принимающая система требует полностью сохранить исходный запрос, дополнительный заголовок можно отключить. Если защита от повторной обработки необходима, её можно включить явно.
При этом ответственность остаётся распределённой между участниками интеграции:
- Adal передаёт стабильный идентификатор одной операции;
- принимающая система сохраняет его;
- база данных обеспечивает уникальность;
- бизнес-логика не выполняет побочные действия повторно.
Только совместная работа этих компонентов делает повторную доставку безопасной.
Надёжность — это не отсутствие повторов
Повторная доставка вебхука не всегда свидетельствует об ошибке в системе.
Напротив, она часто является следствием механизма, который пытается не потерять данные в условиях нестабильной сети. Отправитель повторяет запрос, потому что не получил достоверного подтверждения результата.
Поэтому надёжная архитектура не должна исходить из предположения, что каждый вебхук придёт ровно один раз.
Более безопасное предположение звучит иначе: вебхук может быть доставлен повторно, но бизнес-операция должна быть выполнена один раз.
Ключи идемпотентности позволяют явно идентифицировать операцию и отличать новую команду от технического повтора. Повторные попытки, в свою очередь, уменьшают вероятность потери события.
Эти механизмы не заменяют друг друга. Они решают две противоположные, но взаимосвязанные задачи:
- повторные попытки защищают от потери запросов;
- идемпотентность защищает от повторного выполнения действий.
Вместе они позволяют строить интеграции, которые остаются предсказуемыми даже тогда, когда сеть, серверы или внешние сервисы работают неидеально.
Для современных цифровых систем это не дополнительная оптимизация, а один из базовых принципов надёжной автоматизации.