Вебхуки URL-скорочувача Elido надсилають POST з підписаним JSON-конвертом на вашу кінцеву точку HTTPS щоразу, коли щось змінюється в робочому просторі: посилання створено, відредаговано, видалено, минув термін дії чи досягнуто ліміт кліків, запрошено учасника, верифіковано домен. Кожен запит несе заголовок X-Webhook-Signature: v1=<hex>, що є HMAC-SHA256 над {timestamp}.{raw_body}, а невдала доставка отримує три спроби приблизно за двадцять хвилин.
Чого вона не надсилає сьогодні - це webhook кліку по посиланню. Кліки натомість виходять через API аналітики і пересилачі подій, і я покажу де саме наприкінці. Цей матеріал - вихідна половина поверхні API; швидкий старт з API + SDK URL-скорочувача розглядає вхідну половину, а пояснення розумних посилань - наріжний матеріал кластера функцій, звідки походять події посилань.
Які події посилань сьогодні запускають webhook
Кожна подія нижче доходить до кінцевої точки webhook, яка підписалася на неї за назвою. Форма нової кінцевої точки в дашборді пропонує чекбокси для восьми найпоширеніших; API приймає будь-яку назву зі списку.
| Подія | Спрацьовує, коли | Чекбокс у дашборді |
|---|---|---|
link.created | Посилання створено, поодинці чи під час масового імпорту | так |
link.updated | Змінюється ціль, налаштування чи статус, масові редагування, відновлення | так |
link.deleted | Посилання видалено, поодинці чи масово | так |
link.expired | У посилання минає термін дії | лише API |
link.cap_reached | Посилання досягає максимальної кількості кліків | лише API |
link.broken | Перевірка непрацюючих посилань бачить, що ціль не відповідає | лише API |
workspace.created, workspace.updated | Робочий простір створено або змінено його налаштування | так |
member.invited, member.removed | Учасника додано (напряму, через SCIM чи прийняте запрошення) або видалено | так |
member.role_changed | Роль учасника змінилася | лише API |
invitation.created, invitation.accepted | Запрошення надіслано чи прийнято | лише API |
domain.verified, domain.ssl_failed | Власний домен проходить перевірки DNS, або досі не проходить їх через 24 години | лише API |
audit.event | Будь-який запис журналу аудиту | так |
Зашифровані посилання додають ще дві: link.encrypted_created і link.encryption_rotated. Якщо ви не хочете вручну підтримувати список актуальним, створіть кінцеву точку siem: вона отримує кожну подію в робочому просторі, включно із записами аудиту, взагалі без фільтра підписки.
У дорожній карті, але ще не в роботі: click.created (семплований потік кліків), події білінгу на кшталт billing.subscription_upgraded і фільтри для окремої кінцевої точки на кшталт «лише посилання в теці X». Не підписуйтеся на ці назви сьогодні; їх ніхто не публікує.
Створення кінцевої точки webhook через API
Кінцева точка належить одному робочому простору. Ви створюєте її POST-запитом на /v1/workspaces/{workspace_id}/webhooks:
curl -X POST "https://api.elido.app/v1/workspaces/$WORKSPACE_ID/webhooks" \
-H "Authorization: Bearer elido_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/elido",
"events": ["link.created", "link.updated", "link.deleted"],
"description": "CRM sync",
"kind": "event"
}'
Відповідь - 201 з кінцевою точкою і, для типів event і siem, одноразовим секретом:
{
"endpoint": {
"id": 7,
"workspace_id": 42,
"url": "https://hooks.example.com/elido",
"events": ["link.created", "link.updated", "link.deleted"],
"is_active": true,
"description": "CRM sync",
"kind": "event",
"config": {},
"created_at": "2026-09-21T09:12:44Z"
},
"secret": "whsec_9f2c..."
}
Elido генерує секрет; ви його не надсилаєте. Скопіюйте його зараз, бо жоден пізніший виклик його не поверне. kind приймає п'ять значень. event і siem - це підписані JSON-доставки, які розглядає цей матеріал. discord, telegram і sentry переформатовують ті самі події у повідомлення чату чи подію Sentry, автентифікуються через URL-адресу чи зашифрований токен бота, і не несуть заголовків HMAC.
Дозволи змінилися цього місяця. Читання кінцевих точок і журналу доставок потребує workspace.view. Створення, редагування, видалення, ротація секрету чи повторне надсилання доставки потребує workspace.edit, тобто ролі адміністратора чи власника. API-ключ працює всередині робочого простору, для якого його видали, і ніколи не вище ролі, обраної під час створення, тож ключ рівня viewer отримає 403 на POST вище. Повний довідник: документація з вебхуків.
Конверт payload webhook
Кожна підписана доставка має той самий конверт з чотирьох полів. data містить запис, що змінився, тож для подій посилань це рядок посилання:
{
"type": "link.created",
"workspace_id": 42,
"data": {
"id": 91834,
"workspace_id": 42,
"domain_id": 3,
"slug": "spring-sale",
"destination_url": "https://shop.example.com/spring",
"title": "Spring sale landing",
"tags": ["newsletter"],
"status": "active",
"expires_at": null,
"max_clicks": null,
"redirect_status": 302,
"created_by_user_id": 17,
"created_at": "2026-09-21T09:14:02.184311Z"
},
"timestamp": "2026-09-21T09:14:02Z"
}
Цей приклад скорочено; реальний data несе кожен стовпець посилання, включно з правилами таргетингу, текою, кампанією і полями сканування. У тілі немає ні ID події, ні short_url, тож будуйте коротку URL-адресу з вашого домену і slug, якщо вона вам потрібна. Заплановані події надсилають менший об'єкт: link.expired має link_id, slug і destination_url, а link.cap_reached додає cap і clicks.
Одне виправлення, про яке варто знати, якщо ви логували payload до цього тижня: секретні поля тепер видаляються з payload перед тим, як він покидає Elido. password_hash посилання, захищеного паролем, і token запрошення раніше з'являлися в data; тепер вони не з'являються ні в нових доставках, ні в журналі доставок. Якщо ви зберігали старі payload, ці дані варто видалити.
Перевірка заголовка X-Webhook-Signature
Кожен підписаний запит несе ці заголовки:
X-Webhook-Signature: v1=5d8f0c3e...
X-Elido-Signature: v1=5d8f0c3e...
X-Webhook-Timestamp: 1790068442
X-Webhook-Event: link.created
X-Webhook-Delivery: 55120
User-Agent: Elido-Webhooks/1.0
Обидва заголовки підпису містять однакове значення. Elido обчислює HMAC-SHA256, як визначено в RFC 2104, з усім рядком whsec_... як ключем, над міткою часу, крапкою і сирими байтами тіла. Hex-дайджест отримує префікс v1=. Усередині заголовка немає поля t=; мітка часу живе у власному заголовку.
У Node підписуйте сирі байти, а не повторно серіалізований об'єкт. Express потребує express.raw({ type: "application/json" }) на цьому маршруті саме тому:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyElido(secret, headers, rawBody, toleranceSec = 300) {
const ts = headers["x-webhook-timestamp"] ?? "";
if (!/^\d+$/.test(ts)) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false;
const mac = createHmac("sha256", secret).update(`${ts}.`).update(rawBody);
const expected = Buffer.from("v1=" + mac.digest("hex"));
// During a rotation the old secret signs X-Elido-Signature-Previous.
return ["x-elido-signature", "x-elido-signature-previous"].some((name) => {
const got = Buffer.from(headers[name] ?? "");
return got.length === expected.length && timingSafeEqual(got, expected);
});
}
Перевірка довжини важлива: timingSafeEqual Node кидає виняток на буферах різного розміру замість повернення false. Версія на Python зі стандартним модулем hmac:
import hashlib
import hmac
import time
def verify_elido(secret: str, headers, raw_body: bytes, tolerance: int = 300) -> bool:
ts = headers.get("X-Webhook-Timestamp", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > tolerance:
return False
digest = hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256)
expected = "v1=" + digest.hexdigest()
for name in ("X-Elido-Signature", "X-Elido-Signature-Previous"):
got = headers.get(name)
if got and hmac.compare_digest(got, expected):
return True
return False
Якщо перевірка продовжує не проходити, посібник із перевірки підписів вебхуків містить версію для Go, вузол Code в n8n та типові причини невідповідності. П'ятихвилинне вікно - це ваша перевірка, не наша. Elido проставляє свіжу мітку часу на кожній спробі, включно з повторами, тож легітимний повтор ніколи не виглядає застарілим. Без цього вікна будь-хто, хто перехопив один запит, міг би відтворити його наступного тижня, і підпис все одно збігся б.
Ротація - це POST /v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret, або кнопка Rotate на сторінці кінцевої точки. Ви отримуєте новий секрет один раз. Наступні сім днів кожна доставка також несе X-Elido-Signature-Previous, підписаний старим секретом, тому обидві функції вище пробують і його. Розгорніть новий секрет у будь-який момент цього тижня, і нічого не зламається.
Політика повторних спроб webhook
Воркер доставки підхоплює доставки, що очікують, кожні кілька секунд, тож зміна посилання зазвичай доходить до вас за секунди. Будь-яка відповідь 2xx позначає доставку виконаною. Статус, відмінний від 2xx, мережева помилка чи відсутність відповіді протягом 10 секунд рахується як невдала спроба.
Три спроби на доставку, двадцять хвилин загалом. Це навмисно коротко, і чесно кажучи, коротше, ніж я б обрав для отримувача за нестабільним VPN. Двогодинний простій на вашому боці не покриють автоматичні повтори. Що його покриває - журнал доставок: GET /v1/workspaces/{workspace_id}/webhooks/{id}/deliveries перелічує кожну доставку зі статусом, HTTP-кодом, затримкою, кількістю спроб і часом наступного повтору, а сторінка кінцевої точки показує ті самі рядки з кнопкою Retry. Retry, або POST .../deliveries/{delivery_id}/retry, перезапускає невдалу чи доставлену доставку зі свіжим бюджетом у три спроби і повертає 202. Доставка, що ще очікує, отримує 409.
Деякі збої пропускають повторні спроби. Кінцева точка Telegram без chat_id, чи некоректний DSN Sentry, позначаються невдалими одразу, бо повторення запиту не виправить конфігурацію. Щоб вимкнути кінцеву точку, не видаляючи її, надішліть PUT з "is_active": false; призупинені кінцеві точки не отримують нових доставок.
Якщо ваш обробник виконує важку роботу, спершу поверніть 200 і поставте завдання в чергу. Десятисекундне відсічення - саме те місце, де повільний, але успішний обробник перетворюється на дублікат, а це підводить нас до дедублікації.
Плануєте отримувача для вашої команди? Сторінка функції вебхуків показує сторону дашборду всього цього.
Ідемпотентність і порядок webhook
Elido доставляє за принципом at-least-once. Розділ про повторні спроби показав один спосіб виникнення дубліката: ваш обробник комітить роботу, потім не встигає в десятисекундне вікно, і Elido надсилає її знову. Ручний Retry надсилає повторно навмисно.
В обох випадках зберігається те саме значення X-Webhook-Delivery, бо це ID однієї доставки на одну кінцеву точку, а не однієї спроби. Ключуйте на ньому:
CREATE TABLE elido_webhook_seen (
delivery_id BIGINT PRIMARY KEY,
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- in the handler, inside the same transaction as your work:
INSERT INTO elido_webhook_seen (delivery_id) VALUES ($1)
ON CONFLICT (delivery_id) DO NOTHING
RETURNING delivery_id;
-- no row back means you've already handled this delivery
Дві кінцеві точки, підписані на ту саму подію, отримують два різні ID доставки, тож дедублікуйте для кожної кінцевої точки окремо. У рідкісних перезапусках на нашому боці подія може бути поставлена в чергу двічі як окремі доставки; якщо подвійний запис зашкодить, додайте другий захист на type плюс data.id плюс data.updated_at.
Гарантії порядку немає. Доставки виходять у порядку від найстарішої простроченої, але повтор ранньої події може прийти після пізнішої. Порівнюйте data.updated_at з тим, що ви вже зберегли, перш ніж перезаписувати посилання, і не покладайтеся на timestamp конверта для впорядкування: він має точність лише до секунди.
Де живуть дані кліків замість webhook кліків
Це та частина, де стара версія цього матеріалу помилялася. Сьогодні немає webhook click, і click.created запланований, а не реалізований. Шлях редиректу навмисно лишається вільним від синхронної роботи, що пояснює матеріал прийом кліків за принципом fire-and-forget, а кліки йдуть у сховище аналітики, а не в чергу webhook.
Для даних на рівні кліків зараз у вас є два шляхи:
- Пересилачі подій. Кожен клік по короткому посиланню стає подією на боці сервера в інструменті, який ви вже використовуєте: клікові події Mixpanel, клікові події Klaviyo в профілях чи метрики редиректу Datadog для операційних дашбордів.
- API аналітики.
GET /v1/analytics/workspaces/{workspace_id}/clicks/recentповертає останні кліки, аclicks.csvекспортує їх, тож заплановане завдання може забирати потрібні рядки.
Який варіант підходить, залежить від затримки і того, куди потрапляють дані; вебхуки проти опитування для відстеження кліків розглядає цей компроміс. І якщо семплований потік click.created вийде в реліз, ця сторінка повідомить про це першою.
Прочитайте наріжний матеріал: пояснення розумних посилань.
Пов'язане в блозі
- Вебхуки проти опитування для відстеження кліків - коли штовхати, а коли тягнути.
- Швидкий старт з API + SDK URL-скорочувача - вхідна поверхня API.
- Прийом кліків за принципом fire-and-forget - чому кліки ніколи не чекають на вихідні виклики.
- Клікові події Mixpanel - дані кліків як події на боці сервера.
- Метрики редиректу посилань Datadog - стан редиректу на операційному дашборді.
- Перевірка підписів вебхуків - HMAC-перевірки в Node, Python, Go та n8n, а також налагодження невідповідності.
Поширені запитання
Чи надсилає Elido webhook на кожен клік по посиланню?
Сьогодні - ні. Вебхуки покривають зміни в робочому просторі, такі як link.created, link.updated, link.expired і member.invited. Подія click.created - в дорожній карті як семплований потік. Для даних на рівні кліків зараз використовуйте API аналітики або пересилач подій на кшталт Mixpanel, Klaviyo чи Datadog.
Як перевірити підпис webhook Elido?
Обчисліть HMAC-SHA256 над значенням X-Webhook-Timestamp, крапкою і сирим тілом запиту, з ключем - вашим секретом whsec_. Закодуйте в hex, додайте префікс v1= і порівняйте за постійний час із заголовком X-Webhook-Signature. Відхиляйте мітки часу, старіші за п'ять хвилин.
Скільки разів Elido повторює невдалий webhook?
Кожна доставка отримує три спроби: одну одразу, одну через п'ять хвилин після збою і ще одну через п'ятнадцять хвилин після цього. Будь-який статус, відмінний від 2xx, мережева помилка чи відповідь повільніша за десять секунд рахується як збій. Після третьої спроби доставка позначається невдалою, доки ви не натиснете Retry.
У чому різниця між X-Webhook-Signature і X-Elido-Signature?
Нічого, крім назви. Обидва заголовки несуть той самий підпис v1=, і X-Webhook-Signature лишається для старіших отримувачів. Під час ротації секрету Elido також надсилає X-Elido-Signature-Previous, підписаний старим секретом, протягом семи днів.
Як зупинити повторну обробку того самого webhook?
Збережіть значення заголовка X-Webhook-Delivery і пропускайте будь-який запит, значення якого ви вже обробили. Він ідентифікує одну доставку на одну кінцеву точку і лишається незмінним при автоматичних повторах і ручних повторних надсиланнях, тож унікального індексу на ньому достатньо.
Хто може створювати чи видаляти вебхуки в робочому просторі?
Адміністратори і власники робочого простору. Перелік кінцевих точок і читання журналу доставок потребує доступу на перегляд; створення, редагування, видалення, ротація секрету чи повторне надсилання доставки потребує права workspace.edit. API-ключі обмежені роллю, обраною під час створення ключа.
Спробуйте Elido
Вставте URL - отримайте коротке посилання
Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.
Безкоштовно, без реєстрації · 2 на день