Документация / Вебхуки
Вебхуки
Kivix Pay сам сообщает вашему серверу о событиях — например, что счёт оплачен. Не нужно опрашивать API.
Настройка
Задайте HTTPS-адрес в боте (Kivix Pay → приложение → 🔗 Webhook) или через API, затем пришлите тестовое событие:
pay.webhook.set("https://shop.example/kivix") pay.webhook.test() # придёт событие webhook.test
await pay.webhook.set("https://shop.example/kivix"); await pay.webhook.test();
$pay->webhook->set('https://shop.example/kivix');
$pay->webhook->test();
curl -X PUT https://kivix.cc/v1/webhook -H "Authorization: Bearer $KIVIX_PAY_KEY" \
-H "Content-Type: application/json" -d '{"url":"https://shop.example/kivix"}'
curl -X POST https://kivix.cc/v1/webhook/test -H "Authorization: Bearer $KIVIX_PAY_KEY"
GET
/v1/webhook— адрес и статистика доставокPUT
/v1/webhook— {"url": "https://…"}DELETE
/v1/webhook— отключитьPOST
/v1/webhook/test— прислать webhook.testАдрес — только HTTPS на порту 443, без логина в URL, не локальный и не во внутренней сети.
События
{
"id": "Qm3xR8vLp2KdT6wZ0c",
"object": "event",
"type": "invoice.paid",
"created_at": "2026-10-26T12:31:05Z",
"data": { "invoice": { /* объект счёта, status = "paid" */ } }
}| Событие | Когда |
|---|---|
invoice.paid | Счёт оплачен — выдавайте товар |
webhook.test | Проверка из POST /v1/webhook/test |
Проверка подписи
Заголовок Kivix-Signature: t=1761481865,v1=5f2c…, где v1 = HMAC-SHA256(секрет вебхуков, t + "." + тело_запроса) в hex. Берите тело как пришло, до разбора JSON, и отклоняйте запросы старше 5 минут. С SDK — одна строка:
from kivix_pay import construct_event, SignatureVerificationError @app.post("/kivix") # Flask def kivix_webhook(): try: event = construct_event(request.get_data(), request.headers.get("Kivix-Signature"), WEBHOOK_SECRET) except SignatureVerificationError: return "", 400 if event.type == "invoice.paid": fulfill(event.data.invoice.order_id) return "", 200
import express from "express"; import { constructEvent } from "kivix-pay"; app.post("/kivix", express.raw({ type: "application/json" }), (req, res) => { let event; try { event = constructEvent(req.body, req.get("Kivix-Signature"), process.env.KIVIX_WEBHOOK_SECRET); } catch { return res.sendStatus(400); } if (event.type === "invoice.paid") fulfill(event.data.invoice.order_id); res.sendStatus(200); });
use Kivix\Pay\{Webhook, SignatureVerificationException}; try { $event = Webhook::constructEvent(file_get_contents('php://input'), $_SERVER['HTTP_KIVIX_SIGNATURE'] ?? null, getenv('KIVIX_WEBHOOK_SECRET')); } catch (SignatureVerificationException) { http_response_code(400); exit; } if ($event['type'] === 'invoice.paid') { fulfill($event['data']['invoice']['order_id']); }
Без SDK
import hashlib, hmac, time def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool: parts = dict(item.split("=", 1) for item in header.split(",")) timestamp, signature = parts.get("t", ""), parts.get("v1", "") if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance: return False expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature)
import crypto from "node:crypto"; function verify(rawBody, header, secret, tolerance = 300) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const t = Number(parts.t); if (!t || Math.abs(Date.now() / 1000 - t) > tolerance) return false; const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex"); return parts.v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)); }
$raw = file_get_contents('php://input'); parse_str(str_replace(',', '&', $_SERVER['HTTP_KIVIX_SIGNATURE'] ?? ''), $parts); $t = $parts['t'] ?? ''; $expected = hash_hmac('sha256', $t . '.' . $raw, getenv('KIVIX_WEBHOOK_SECRET')); $ok = ctype_digit($t) && abs(time() - (int) $t) <= 300 && hash_equals($expected, $parts['v1'] ?? '');
Доставка и повторы
- Любой ответ
2xx— доставлено. Иначе повторяем с нарастающей паузой, до 8 попыток. - Обрабатывайте событие один раз: сохраняйте
id(он же в заголовкеKivix-Event-Id) и пропускайте повторы. - Отвечайте быстро, тяжёлую работу делайте в фоне.
- Пропустили что-то?
GET /v1/events— последние события и статус их доставки.
| Заголовок | Значение |
|---|---|
Kivix-Signature | t=<unix>,v1=<hex> |
Kivix-Event-Id | id события |
Kivix-Event-Type | тип события, например invoice.paid |