Skip to content

Webhooks

Webhooks

С уебхукове получавате известия в реално време, когато възникнат събития във вашия workspace — например когато се сканира QR код.

Създаване на webhook

Terminal window
POST /v1/webhooks
{
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"secret": "my-secret-key-min-16-chars"
}

Отговор

{
"id": "wh_abc123",
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"is_active": true,
"secret": "my-secret-key-min-16-chars",
"secret_hint": "my-s…",
"created_at": "2026-03-15T10:00:00.000Z"
}

Типове събития

СъбитиеОписание
*Всички събития
qr.createdСъздаден QR код
qr.updatedОбновен QR код (URL, статус, тагове)
qr.deletedИзтрит QR код
qr.scannedСканиран QR код
qr.flaggedQR кодът е маркиран като небезопасен
webhook.pingТестов пинг (чрез /ping крайна точка)

Формат на payload

Всички payloads на уебхуковете имат следния формат:

{
"id": "evt_4f0c2a91d7e84b3c9a1f",
"type": "qr.scanned",
"created": "2026-03-15T10:00:00.000Z",
"data": {
"code_id": "qr_abc123",
"short_code": "r7f3Kx",
"scanned_at": "2026-03-15T10:00:00.000Z",
"country": "AT",
"device_type": "mobile",
"os": "iOS",
"browser": "Safari",
"language": "de",
"ab_variant": null,
"redirected_to": "https://example.com/landing"
}
}

Събития за кодове: qr.created и qr.updated

При тези две събития data е кодът точно във вида, в който GET /v1/codes/:id го връща. Това включва appearance – цветовете, записани за кода (null означава стандартното черно или бяло):

{
"id": "evt_9b2e41c07a5d4f18b3c6",
"type": "qr.updated",
"created": "2026-09-28T10:00:00.000Z",
"data": {
"id": "qr_abc123",
"short_code": "r7f3Kx",
"status": "live",
"appearance": { "foreground_color": "#1F4E79", "background_color": null },
"updated_at": "2026-09-28T10:00:00.000Z"
}
}

Примерът е съкратен. Всички полета са налични в API за кодове.

Верификация на сигнатура

qr3.app подписва всяка заявка към уебхук с HMAC-SHA256:

X-QR3-Signature: sha256=a1b2c3d4e5f6...
X-QR3-Event: qr.scanned
X-QR3-Delivery: wdl_abc123
X-QR3-Attempt: 1
User-Agent: qr3-webhooks/1.0

Имплементиране на верификация

import crypto from "crypto";
function verifySignature(
payload: string,
signature: string | undefined,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
const expectedBytes = Buffer.from(`sha256=${expected}`);
const receivedBytes = Buffer.from(signature ?? "");
return (
expectedBytes.length === receivedBytes.length &&
crypto.timingSafeEqual(expectedBytes, receivedBytes)
);
}
// In Express: verify the raw body — re-serialised JSON breaks the signature.
app.post(
"/webhooks/qr3",
express.raw({ type: "application/json" }),
(req, res) => {
const raw = req.body.toString("utf8");
const signature = req.headers["x-qr3-signature"] as string | undefined;
if (!verifySignature(raw, signature, process.env.QR3_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(raw);
console.log(event.type, event.data);
res.json({ received: true });
},
);

Логика за повторен опит

При грешки (HTTP >= 300 или изтичане на времето) qr3.app опитва повторно доставянето:

ОпитЗакъснение
1Веднага
21 минута
35 минути

След 10 последователни грешки уебхукът се деактивира автоматично.

Гаранция за доставка

qr3.app доставя поне веднъж, а не точно веднъж. Затова едно и също събитие може да достигне до вашата крайна точка повече от веднъж: като повторен опит след грешка или като повторение на опит, който вече е изпратен.

  • Премахвайте дубликатите с помощта на идентификатора id от payload (evt_…). Той принадлежи на събитието и остава един и същ при всеки опит и при всяко повторение. X-QR3-Delivery обозначава отделния опит: повторение на този опит носи същата стойност, нов опит — нова. Потвърждавайте вече известен id с 2xx, а не с 4xx: 4xx се брои за неуспешен опит, а след 10 последователни грешки уебхукът се деактивира автоматично.
  • Опит, който един час след създаването си все още е pending (например защото съобщението му в опашката е изгубено), се поставя на failed от ежедневно почистване; status_code и response_time_ms остават null. Това не е грешка на вашата крайна точка и не се брои към 10-те последователни грешки. Дневникът записва за него текста Delivery was not completed in time — closed by cleanup; крайната точка за логовете за доставка не го връща. Затварянето не е окончателно: ако съобщението все пак пристигне, то се доставя, а истинският му резултат заменя този текст.

Логове за доставка

Terminal window
GET /v1/webhooks/:id/deliveries
{
"data": [
{
"id": "wdl_abc123",
"event_type": "qr.scanned",
"status": "success",
"status_code": 200,
"response_time_ms": 145,
"attempt": 1,
"created_at": "2026-03-15T10:00:00.000Z"
}
]
}

Опитите за доставка се съхраняват 90 дни (текущият UTC ден плюс предходните 89 дни) и след това се изтриват автоматично. Освен показаните полета дневникът съхранява payload на събитието и първия килобайт (1 KB) от отговора на крайната точка; тази крайна точка не връща нито едното, нито другото.

Тестов пинг

Тествайте уебхук веднага:

Terminal window
POST /v1/webhooks/:id/ping

Това изпраща събитие webhook.ping към URL адреса на вашата крайна точка.