HexблогСтать партнёром

Вебхуки и повторы: как не потерять деньги на продлениях

VPN-франшиза5 сентября 2026 г. 8 мин чтения

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

Чем подписка отличается от заказа

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

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

Ловушка первая: двойное продление

Клиент жмёт «Продлить», страница задумывается на пару секунд, он жмёт ещё раз. Если идентификатор операции у обоих запросов один, второй вернёт результат первого и денег не спишет. Если разный, вы продлили подписку дважды и списали с депозита две суммы.

Правило простое: идентификатор операции генерируется один раз в момент, когда человек нажал кнопку, и сохраняется у вас вместе с заказом до того, как ушёл запрос. Не в момент отправки, не в цикле повторов, а один раз на одно намерение пользователя. Повтор из-за таймаута сети берёт тот же идентификатор и потому безопасен.

Ловушка вторая: гонка с автопродлением

Если у подписки включено автопродление на стороне платформы, а вы дополнительно продлеваете её своим кодом, оба продления однажды случатся в одну минуту. Формально ошибки нет, оба списания законные. Клиент при этом заплатил один раз, а месяцев получил два, и разницу оплатили вы.

Решать это надо не блокировками, а выбором: либо продлением управляет платформа, либо ваш код. Смешивать два источника продления на одной подписке не стоит. Если продлеваете сами, автопродление держите выключенным и реагируйте на события об окончании срока.

Ловушка третья: истёкший срок

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

Отдельно про заморозку: пока подписка заморожена, дни не идут. Значит, любые ваши расчёты вида «куплено 2 сентября, значит кончится 2 октября» будут врать. Дату окончания берите из ответа API.

Приём события: три обязательных шага

Обработчик события у вас должен делать ровно три вещи, и в таком порядке:

  • проверить подпись по сырому телу запроса, до разбора JSON. Подпись считается по байтам, и пересобранный из объекта JSON даст другую строку;
  • проверить, не обрабатывали ли вы уже это событие. Повторы приходят штатно, если ваш сервер ответил медленно или не ответил вовсе;
  • быстро ответить кодом 2xx, а тяжёлую работу сделать после. Письма, начисления и походы в другие сервисы внутри обработчика превращают повтор в лавину.

Когда вебхук молчит

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

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

Депозит кончился посреди месяца

Отдельный сценарий, который забывают заложить. Депозит опустел, покупка отбивается отказом, а у вас на этот случай нет ни письма себе, ни понятного текста покупателю. Человек видит непонятную ошибку, вы узнаёте о проблеме из его сообщения.

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

Чек-лист перед первым платящим клиентом

  • идентификатор операции создаётся один раз на действие и сохраняется до запроса;
  • повтор запроса после таймаута не списывает деньги второй раз;
  • подпись события проверяется по сырому телу, а не по пересобранному JSON;
  • повторное событие с тем же идентификатором не начисляет ничего дважды;
  • дата окончания берётся из ответа API, а не считается у вас;
  • автопродление и своё продление не работают на одной подписке одновременно;
  • отказ по депозиту виден вам и понятен покупателю.

Ключ выдаётся в кабинете партнёра

Выдавайте и продлевайте VPN-подписки из своего сервиса. Оплата с депозита по вашей закупочной цене, розницу назначаете вы.

Читайте также

Hex

© Hex, white-label франшиза

Собираем анонимную аналитику посещений. Подробнее