Что такое Webhook

Webhook — это механизм, который позволяет вашей системе получать автоматические уведомления о событиях через HTTP-запросы. Когда в нашей системе происходит определенное событие (например, рассылка завершена), мы отправляем POST-запрос с детальной информацией на указанный вами URL.

Настройка Webhook

Вы можете настроить вебхуки в веб-панели управления:

  1. Перейдите в раздел «Интеграции».
  2. Нажмите на карточку «Webhooks».
  3. Нажмите «Создать webhook».
  4. Заполните поля:
    • Название — для вашей личной идентификации вебхука.
    • URL — адрес вашего сервера, который будет принимать уведомления.
    • События — выберите из списка события, о которых хотите получать уведомления.
  5. Нажмите «Создать Webhook».

После создания вебхук появится в общем списке. На его карточке отображается вся ключевая информация:

  • Статус (активен / неактивен)
  • Название и URL
  • Список отслеживаемых событий
  • Время последней успешной отправки
  • Активность (общее число отправленных событий)

В меню (⋮) в углу карточки доступны следующие действия:

  • Тест
  • Редактировать
  • Активировать / Деактивировать
  • Удалить

Формат уведомлений

{
  "eventId": "<uuid>",
  "trigger": "<entity>.<event>",
  "occurredAt": "2023-10-27T12:35:01.123Z",
  "payload": {
    "someStringField": "string",
    "numberField": 42
  }
}

Поля запроса:

  • eventId — уникальный идентификатор события.
  • trigger — тип события в формате сущность.событие (см. список ниже).
  • occurredAt — время возникновения события в формате ISO 8601 (UTC).
  • payload — объект с данными, специфичными для каждого события.

Тестовое событие

При нажатии на кнопку «Тест» в меню (⋮) в углу карточки вы получите POST-запрос со следующим payload:

{
  "eventId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "trigger": "rusender.webhook_test",
  "occurredAt": "2030-12-31T20:59:59.999Z",
  "payload": {
    "timestamp": "2030-12-31T20:59:59.999Z",
    "stringField": "someString",
    "numberField": 42,
    "booleanField": true,
    "objectField": {
      "nestedString": "value",
      "nestedNumber": 123
    },
    "arrayField": ["item1", "item2", 3, false]
  }

Доступные триггеры

Сущность: Почтовые рассылки (mail_distribution)

Общий Payload для событий рассылки

Для событий mail_distribution.startedmail_distribution.completedmail_distribution.moderatingmail_distribution.approved и mail_distribution.rejected используется следующая структура payload:

{
  "mailDistributionId": 12345,
  "mailDistributionName": "Название рассылки",
  "mailDistributionSubject": "Тема письма"
}
  • mail_distribution.started — рассылка запущена.
  • mail_distribution.completed — рассылка успешно завершена.
  • mail_distribution.moderating — рассылка отправлена на модерацию.
  • mail_distribution.approved — рассылка одобрена модератором.
  • mail_distribution.rejected — рассылка отклонена модератором.

mail_distribution.banned

Рассылка заблокирована. Это событие имеет расширенный payload.

Payload:

{
  "mailDistributionId": 12345,
  "mailDistributionName": "Название рассылки",
  "mailDistributionSubject": "Тема письма",
  "negative": {
    "reason": "hard_bounced",
    "explanation": "Подробное описание причины блокировки"
  }
}

В поле reason будет одна из следующих строк:

  • hard_bounced
  • soft_bounced
  • error_spam
  • complain

Требования к обработке

1. Ответ сервера и таймаут

Ваш сервер должен ответить на запрос в течение 30 секунд.

  • HTTP-статус 200299: означает, что событие успешно принято и обработано.
  • Любой другой статус (3xx4xx5xx) или таймаут: считается ошибкой, после чего мы будем пытаться доставить событие повторно.

2. Идемпотентность

Наша система гарантирует доставку по принципу «как минимум один раз» (at-least-once delivery). Это означает, что из-за сетевых проблем или ошибок на стороне вашего сервера одно и то же событие может быть доставлено повторно. Используйте поле eventId для защиты от дублирующей обработки.

3. Повторные попытки (Retries)

В случае ошибки доставки мы предпримем 6 повторных попыток с увеличивающимся интервалом.

ПопыткаЗадержка после предыдущей
11 минута
22 минуты
34 минуты
48 минут
515 минут
615 минут

Всего мы можем отправить до 7 запросов на одно событие (первая отправка + 6 повторов). После последней неудачной попытки мы прекратим доставку события, и оно будет помечено как «недоставленное».

4. Требования к URL

Ваш URL-обработчик должен использовать протокол HTTPS и иметь валидный SSL-сертификат. Уведомления на HTTP-адреса доставляться не будут.

Пример обработчика

PHP

<?php
// Получаем raw-тело запроса
$input = file_get_contents('php://input');
$data = json_decode($input, true);

if (!$data || !isset($data['eventId'])) {
    http_response_code(400); // Bad Request
    exit('Invalid payload');
}

// Проверяем, что событие не обработано ранее
if (isEventProcessed($data['eventId'])) {
    http_response_code(200);
    exit('OK. Already processed.');
}

// Обрабатываем событие
switch ($data['trigger']) {
    case 'mail_distribution.completed':
        handleMailDistributionCompleted($data['payload']);
        break;
    case 'mail_distribution.banned':
        handleMailDistributionBanned($data['payload']);
        break;
    // ... другие события
}

// Отмечаем событие как обработанное
markEventAsProcessed($data['eventId']);

http_response_code(200);
echo 'OK';
?>

Node.js (Express)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  const { eventId, trigger, payload } = req.body;

  // Проверяем идемпотентность
  if (isEventProcessed(eventId)) {
    return res.status(200).send('OK. Already processed.');
  }

  // Обрабатываем событие
  switch (trigger) {
    case 'mail_distribution.completed':
      handleMailDistributionCompleted(payload);
      break;
    case 'mail_distribution.banned':
      handleMailDistributionBanned(payload);
      break;
    // ... другие события
  }

  // Отмечаем как обработанное
  markEventAsProcessed(eventId);

  res.status(200).send('OK');
});

Рекомендации по разработке

1. Обрабатывайте запросы асинхронно

Ваш сервер должен ответить HTTP 200 OK как можно быстрее. Не выполняйте длительные операции (запросы к другим API, обработка файлов) прямо в момент получения вебхука. Вместо этого добавьте задачу в очередь (например, RabbitMQ, Redis, SQS) и немедленно верните ответ. Это защитит вас от таймаутов и позволит легко масштабировать обработку.

2. Будьте готовы к новым полям

В будущем в объект payload могут быть добавлены новые поля. Ваш код должен быть устойчив к этому и не падать, если в JSON появятся неизвестные ему ключи.

3. Настройте мониторинг

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

4. Используйте тестовые окружения

Не используйте ваш основной (production) URL для отладки. Создайте отдельный вебхук для тестового или staging-окружения, чтобы безопасно вносить изменения в код.

Часто задаваемые вопросы (FAQ)

Я настроил вебхук, но не получаю уведомлений. Что делать?

Проверьте следующее:

  1. Статус в UI: Убедитесь, что вебхук «Активен».
  2. Тестовое событие: Используйте кнопку «Тест» на карточке вебхука.
  3. Публичный URL: Убедитесь, что URL доступен из интернета (не localhost).
  4. HTTPS: Убедитесь, что URL начинается с https:// и имеет валидный SSL-сертификат.
  5. Логи сервера: Проверьте логи вашего веб-сервера (Nginx, Apache) или приложения на наличие записей о входящих запросах.
Почему вы отправляете одно и то же событие (eventId) несколько раз?

Мы повторяем отправку, если не получаем от вашего сервера подтверждение об успешной доставке (ответ с HTTP-кодом 200-299) в течение 30 секунд. Это может произойти по двум основным причинам:

  1. Ваш сервер вернул ошибку (например, статус 500 Internal Server Error).
  2. Произошла сетевая проблема, из-за которой мы не получили ответ, даже если ваше приложение его отправило и успешно обработало событие.

Поэтому ваша система всегда должна быть готова к дубликатам (см. раздел «Идемпотентность»).

Гарантируется ли порядок доставки событий?

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

Что произойдет, если мой сервер будет недоступен долгое время?

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

Могу ли я использовать один и тот же URL для нескольких вебхуков?

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

Дата публикации Дата публикации: 2 июля 2025 Обновлено: 9 июля 2025