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

Отправляйте подписанные HTTP POST-запросы с Elido на свой сервер при изменениях ссылок, доменов, участников и воркспейса. Заголовки, полезная нагрузка, повторы доставки, журнал доставок и ротация секрета.

8 мин чтенияОбновлено 2026-09-22

Что вы настроите

  • Конечную точку, которая принимает события Elido, такие как link.created, link.updated, link.deleted, member.invited и domain.verified.
  • Проверку HMAC-SHA256 заголовка X-Elido-Signature, чтобы ваш сервер мог подтвердить, что каждая доставка пришла от Elido.
  • Три попытки доставки для каждого события, журнал доставок с кнопкой ручного Retry и ротацию секрета с 7-дневным периодом перекрытия.

Вебхуки отправляют события с Elido на ваш сервер по мере их возникновения, поэтому вам не нужно опрашивать API. Типичные сценарии: синхронизация метаданных ссылок с CRM, поддержание внутреннего индекса ссылок в актуальном состоянии или запуск задания при публикации ссылки.

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

Добавление конечной точки вебхука

  1. Перейдите в Dashboard → Webhooks.
  2. Введите URL, на который нам следует отправлять POST-запросы, и необязательное описание.
  3. Выберите тип конечной точки: Events (вы выбираете типы событий) или SIEM (firehose) (каждое событие в воркспейсе).
  4. Для конечной точки Events отметьте нужные типы событий. События link.* отмечены по умолчанию.
  5. Нажмите Add endpoint. Секрет подписи появится один раз сразу под формой. Скопируйте его сейчас: Elido больше его не покажет.

URL должен использовать HTTPS. Обычный http:// отклоняется при сохранении конечной точки, кроме localhost и 127.0.0.1, которые по-прежнему разрешены для локальной разработки.

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

Кто может управлять вебхуками

Любой участник воркспейса может просматривать конечные точки и журнал доставок. Для создания, редактирования, приостановки, удаления конечной точки, ротации секрета и повторной попытки доставки требуется разрешение workspace.edit, которым обладают встроенные роли Admin и Owner. API-ключ может обращаться к тем же конечным точкам вебхуков API, если его роль имеет это разрешение.

Типы конечных точек

  • Events (event): получает только типы событий, на которые вы подписались. Подписывается.
  • SIEM (siem): получает каждое событие, опубликованное для воркспейса, включая audit.event для каждой записи журнала аудита. Подписывается. Предназначен для конвейеров журналов и инструментов безопасности.
  • Discord, Telegram и Sentry: каналы чатов и оповещений, которые используют ту же систему доставки, но отправляют собственный формат сообщений поставщика. Они проходят аутентификацию через URL или токен поставщика, поэтому не передают заголовки подписи. См. Discord, Telegram и Sentry.

Всё сказанное ниже о подписях и секретах относится только к конечным точкам Events и SIEM.

Каталог событий

Имена имеют формат <resource>.<action>. Форма в Dashboard предлагает самые распространённые варианты; через API можно подписать конечную точку Events на любой тип из этого списка, а конечная точка SIEM получает их все.

В селекторе Dashboard:

  • link.created: создана короткая ссылка, включая ссылки, созданные массово.
  • link.updated: ссылка изменена (назначение, заголовок, теги, срок действия и так далее), восстановлена, изменён её статус или модерация изменила её статус безопасности.
  • link.deleted: ссылка помечена как удалённая без физического удаления.
  • workspace.updated: изменены настройки воркспейса.
  • workspace.created: отправляется при создании воркспейса. Поскольку конечная точка не может существовать в воркспейсе до его создания, обычно вы не будете получать это событие.
  • member.invited: кто-то присоединился к воркспейсу через прямое добавление, принятие приглашения или подготовку через SCIM.
  • member.removed: участник удалён вручную или через SCIM.
  • audit.event: одно событие для каждой записи журнала аудита.

Доступны через API или конечную точку SIEM:

  • link.expired: срок действия ссылки истёк.
  • link.cap_reached: ссылка достигла лимита кликов. data включает cap и clicks.
  • link.broken: проверка работоспособности ссылок Elido обнаружила сбой назначения. data включает status_code, error_message и first_seen_at.
  • link.encrypted_created и link.encryption_rotated: для зашифрованных ссылок.
  • domain.verified: пользовательский домен прошёл проверку DNS.
  • domain.ssl_failed: записи DNS пользовательского домена всё ещё не найдены через 24 часа после его добавления. Несмотря на название, это относится к проверке DNS, а не к продлению сертификата.
  • member.role_changed: изменилась роль участника. data включает old_role, new_role и changed_by_user_id.
  • invitation.created и invitation.accepted.
  • События биллинга: subscription.* (например, subscription.activated, subscription.renewed, subscription.canceled) и usage.threshold_80, usage.threshold_100.

Запланированы и пока не отправляются: link.clicked.aggregated (число кликов по ссылке за период), события отдельных кликов, conversion.recorded, domain.tls_renewed, qr.generated и отдельное событие abuse.flagged. Решение модерации по ссылке сейчас приходит как link.updated с новым abuse_status.

Структура полезной нагрузки

Каждая подписанная доставка представляет собой POST с Content-Type: application/json, User-Agent: Elido-Webhooks/1.0 и таким конвертом:

{
  "type": "link.created",
  "workspace_id": 4123,
  "timestamp": "2026-09-22T09:42:11Z",
  "data": {
    "id": 891234,
    "workspace_id": 4123,
    "domain_id": 12,
    "slug": "spring-2026",
    "destination_url": "https://acme.com/spring-sale",
    "title": "Spring sale",
    "tags": ["email"],
    "status": "active",
    "expires_at": null,
    "created_by_user_id": 5821,
    "created_at": "2026-09-22T09:42:11Z"
  }
}
  • type - тип события. Он также указан в заголовке X-Webhook-Event.
  • timestamp - время возникновения события в формате UTC.
  • data зависит от события. Для событий link.* это полный объект ссылки (пример выше сокращён). Для остальных событий это небольшой объект с идентификаторами и полями, перечисленными в каталоге.
  • Секреты никогда не покидают Elido: такие поля, как хеш пароля ссылки или токен приглашения, удаляются из data перед доставкой.

В теле пока нет поля id события; идентификатор для каждого события запланирован. Для идемпотентности используйте заголовок X-Webhook-Delivery, описанный ниже.

Заголовки и подписи

Каждая доставка на конечную точку Events или SIEM содержит:

ЗаголовокЗначение
X-Elido-Signaturev1= и шестнадцатеричная подпись HMAC-SHA256
X-Webhook-SignatureТо же значение, что и у X-Elido-Signature, для старых интеграций
X-Webhook-TimestampВремя Unix в секундах, когда был подписан этот запрос
X-Webhook-EventТип события, например link.created
X-Webhook-DeliveryИдентификатор доставки. Он не меняется при повторах той же доставки
X-Elido-Signature-PreviousТолько во время ротации секрета: та же подпись, созданная старым ключом

Подпись вычисляется так:

v1=hex( HMAC-SHA256( secret, "{X-Webhook-Timestamp}.{raw request body}" ) )

Ключом служит вся строка секрета, включая префикс whsec_, без изменений. Подписывайте полученные необработанные байты, а не повторно сериализованный объект JSON, и сравнивайте их за постоянное время. Короткий пример на Node (Express):

import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const SECRET = process.env.ELIDO_WEBHOOK_SECRET; // full whsec_... value

function same(a, b = "") {
  const x = Buffer.from(a);
  const y = Buffer.from(b);
  return x.length === y.length && timingSafeEqual(x, y);
}

const app = express();
app.post(
  "/webhooks/elido",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const ts = req.get("X-Webhook-Timestamp") ?? "";
    if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
      return res.status(400).send("stale timestamp");
    }
    const expected =
      "v1=" +
      createHmac("sha256", SECRET)
        .update(`${ts}.`)
        .update(req.body)
        .digest("hex");
    const ok = [
      req.get("X-Elido-Signature"),
      req.get("X-Elido-Signature-Previous"),
    ].some((sig) => same(expected, sig));
    if (!ok) return res.status(401).send("bad signature");

    const event = JSON.parse(req.body.toString("utf8"));
    // queue event for processing, then answer quickly
    res.sendStatus(200);
  },
);

Варианты на Python и Go, тестовый пример и распространённые ошибки см. в руководстве по проверке подписей вебхуков.

Ротация секрета подписи

Откройте конечную точку в Dashboard → Webhooks и нажмите Rotate secret. Новый секрет whsec_ будет показан один раз, после чего Elido начнёт подписывать им.

Старый ключ остаётся действительным в течение 7-дневного льготного периода. В этот период каждая доставка содержит две подписи: X-Elido-Signature с новым ключом и X-Elido-Signature-Previous со старым, поэтому получатель, который ещё не переключился, продолжает выполнять проверку. При развёртывании нового секрета принимайте любой из этих заголовков.

Для конечных точек, созданных до 22 сентября 2026 года, есть одно исключение: при их первой ротации заголовок X-Elido-Signature-Previous не создаётся, поэтому исходный секрет сразу перестаёт работать. Сразу после первой ротации установите новый секрет на сервере. Конечные точки, созданные начиная с этой даты, используют обе подписи уже при первой ротации. Начиная со второй ротации действует 7-дневный период перекрытия.

Повторные попытки

Доставка считается неудачной при любом ответе, отличном от 2xx, сетевой ошибке или отсутствии ответа в течение 10 секунд. Для каждой доставки предусмотрено всего 3 попытки:

  1. Первая попытка - через несколько секунд после события.
  2. Повторная попытка - примерно через 5 минут после первого сбоя.
  3. Последняя повторная попытка - примерно через 15 минут после второго сбоя.

Если третья попытка не удалась, доставка помечается как failed, и автоматические повторы прекращаются. Конечная точка остаётся активной, а новые события продолжают доставляться. Ваша конечная точка может увидеть одну и ту же доставку несколько раз (например, если обработала запрос, но ответила после 10-секундного тайм-аута), поэтому устраняйте дубликаты по X-Webhook-Delivery.

Журнал доставок

Откройте конечную точку в Dashboard → Webhooks, чтобы увидеть последние 100 доставок. Для каждой отображаются тип события, статус (pending, delivered или failed), HTTP-статус, возвращённый вашим сервером, время ответа, использованные из 3 попытки, время создания и завершения, время следующей повторной попытки для ожидающих доставок, последняя ошибка и полная отправленная полезная нагрузка.

Для любой доставленной или неудачной доставки есть кнопка Retry. Она повторно ставит в очередь ту же доставку (с тем же идентификатором X-Webhook-Delivery, но с новой подписью) с новым лимитом в 3 попытки. Через API GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries позволяет просматривать более старые доставки постранично с помощью limit (до 100) и offset, а POST .../deliveries/{delivery_id}/retry делает то же, что и кнопка. Если вы не можете самостоятельно воспроизвести доставку, служба поддержки Elido может сделать это за вас.

Запись тела ответа вашего сервера в журнале запланирована.

Локальное тестирование

Запланирована CLI-команда, которая пересылает вебхуки на localhost (npx @elido/cli webhooks forward). Пока она недоступна, откройте доступ к локальному серверу через туннель, например ngrok или Cloudflare Tunnel, добавьте HTTPS-URL туннеля как конечную точку и удалите её, когда закончите.

Ограничения

  • 10 секунд на ответ вашей конечной точки.
  • 3 попытки доставки для каждого события плюс ручные повторы.
  • API журнала доставок возвращает не более 100 доставок на страницу.

Устранение неполадок

Подпись никогда не совпадает. Обычно причины такие: ваш фреймворк разобрал и повторно сериализовал JSON до хеширования (хешируйте необработанное тело), вы удалили префикс whsec_ из ключа или подписываете с использованием timestamp из тела вместо заголовка X-Webhook-Timestamp. В публикации о проверке подписей есть тестовый пример, с которым можно сравнить свой код.

Подпись перестала совпадать после ротации. На сервере всё ещё хранится старый секрет. Установите новый. Для конечной точки, созданной до 22 сентября 2026 года, при первой ротации нет X-Elido-Signature-Previous, на который можно было бы переключиться.

Некоторые события приходят дважды. Elido повторил доставку, потому что конечная точка ответила с ошибкой или работала дольше 10 секунд. Устраняйте дубликаты по X-Webhook-Delivery и отвечайте кодом 2xx до выполнения медленных операций.

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

Это было полезно?
Нужно больше? Напишите команде - ответ в течение одного рабочего дня.Связаться с поддержкой