Webhooks
CertifiEd уведомляет внешние системы о событиях лицензий исходящими webhooks. Доставку выполняет фоновый воркер WebhookDispatchWorker — с ретраями и HMAC-подписью тела запроса.
Тарҷумаи тоҷикии ҳуҷҷатҳо дар ҳоли омодашавӣ аст. Матни зерин ба забони русӣ оварда шудааст.
Управление эндпоинтами доступно только оператору, через panel API /api/v1/panel/webhooks — см. API Reference.
События
Актуальный список отдаёт GET /api/v1/panel/webhooks/event-types (источник — WebhookEventTypes).
| Событие | Когда публикуется | Ключевые поля `data` |
|---|---|---|
license.issued | Выпущена лицензия | licenseId, licenseKey, companyId, templateId, expiresAt |
license.activated | Первая активация на новой машине | licenseId, licenseKey, activationId, hwFingerprint, machineName, clientIp |
license.deactivated | Деактивация активации | licenseId, licenseKey, activationId |
license.revoked | Лицензия отозвана | licenseId, licenseKey, reason, revokedAt |
license.expired | Лицензия истекла | зависит от источника события |
license.heartbeat_missed | Пропущен ожидаемый heartbeat | зависит от источника события |
Эндпоинт подписывается на подмножество событий (events); доставки создаются только для активных (isActive) эндпоинтов, подписанных на данный тип.
Формат доставки
POST на URL эндпоинта, тело — JSON-конверт:
{ "event": "license.issued", "occurredAt": "2026-07-16T14:06:59.1234567+00:00", "data": { "licenseId": "019f6b40-89c4-7d41-be2a-4a151a3c0063", "licenseKey": "CFED-S7FN-RJFA-KAEY-8KZC", "companyId": "019f6b40-0f9c-7efd-996b-0dadd2ed1077", "templateId": "019f6b40-0fb8-78f0-9af2-e3589de54069", "expiresAt": "2027-07-16T14:06:59+00:00" }}| Заголовок | Значение |
|---|---|
Content-Type | application/json |
X-CertifiEd-Event | Тип события, например license.issued |
X-CertifiEd-Delivery | GUID доставки — используйте для идемпотентности на приёмнике |
X-CertifiEd-Signature | sha256={hex} — HMAC-SHA256 тела запроса |
Подпись X-CertifiEd-Signature
X-CertifiEd-Signature: sha256=<hex( HMAC_SHA256(key = endpoint.secret, msg = raw_body) )>- Ключ — секрет эндпоинта (
secret, минимум 16 символов), заданный при создании. - Сообщение — сырые байты тела запроса (UTF-8), ровно как пришли: не переформатируйте JSON перед проверкой.
- Значение — hex в нижнем регистре с префиксом
sha256=.
Проверка на приёмнике — C# (ASP.NET Core)
[HttpPost("/webhooks/certified")]public async Task<IActionResult> Receive(){ // 1. Raw body — before any deserialization. using var reader = new StreamReader(Request.Body); var body = await reader.ReadToEndAsync();
// 2. Expected signature. var expected = "sha256=" + Convert.ToHexStringLower( System.Security.Cryptography.HMACSHA256.HashData( Encoding.UTF8.GetBytes(secret), // the endpoint secret Encoding.UTF8.GetBytes(body)));
var received = Request.Headers["X-CertifiEd-Signature"].ToString();
// 3. Constant-time comparison. var ok = CryptographicOperations.FixedTimeEquals( Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(received)); if (!ok) return Unauthorized();
// 4. Idempotency by X-CertifiEd-Delivery, then handle the event. var deliveryId = Request.Headers["X-CertifiEd-Delivery"].ToString(); // ... dedupe(deliveryId); handle(body); return Ok();}Проверка на приёмнике — Node.js (Express)
const crypto = require('crypto');
// Raw body required: express.raw({ type: 'application/json' })app.post('/webhooks/certified', express.raw({ type: 'application/json' }), (req, res) => { const body = req.body; // Buffer with the raw bytes const expected = 'sha256=' + crypto .createHmac('sha256', secret) // the endpoint secret .update(body) .digest('hex');
const received = req.get('X-CertifiEd-Signature') || ''; const a = Buffer.from(expected); const b = Buffer.from(received); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) { return res.status(401).end(); }
const deliveryId = req.get('X-CertifiEd-Delivery'); const payload = JSON.parse(body.toString('utf8')); // ... dedupe(deliveryId); handle(payload); res.status(200).end();});Retry-политика
Воркер опрашивает очередь раз в 15 секунд и отправляет due-доставки. Успех — это 2xx; любой другой ответ или сетевая ошибка приводят к ретраю.
| Попытка | Задержка до следующей |
|---|---|
| 1 | +1 минута |
| 2 | +5 минут |
| 3 | +30 минут |
| 4 | +2 часа |
| 5 | +12 часов |
| после 5 | статус Failed, ретраи прекращаются |
Таймаут HTTP-запроса доставки — 30 секунд. Тело ответа приёмника сохраняется (до 2000 символов) в responseBody, код ответа — в responseStatusCode, последняя ошибка — в lastError.
Replay (ручная переотправка)
POST /api/v1/panel/webhooks/deliveries/{deliveryId}/replayСбрасывает доставку: attemptCount → 0, статус → Pending, nextAttemptAt → now. На следующем цикле воркер отправит её заново — полезно после починки приёмника или для проверки подписи. История доставок эндпоинта — GET /api/v1/panel/webhooks/{id}/deliveries.