☰ Меню · Вебхуки
Вебхуки
Получайте HTTPS POST, когда что-то происходит: где угодно проведён чек, открыта награда, со стола пришёл заказ.
События охватывают всё, что делается через API в этом заведении, вашей интеграцией и любой другой (другой кассой, киоском, коннектором системы записи). Подписывайтесь только на те события, которые используете.
Добавьте адрес
В разделе Разработчикам → Вебхуки или через API. URL должен быть публичным и работать по HTTPS. Секрет подписи возвращается один раз, при создании адреса.
curl https://loyaltyfy.io/api/v1/webhook_endpoints \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://pos.example.com/loyaltyfy/webhooks",
"events": ["transaction.completed", "order.created"]
}'{
"object": "webhook_endpoint",
"id": "01c2a784-4bcf-43b9-8ac9-ddbad7a25341",
"url": "https://pos.example.com/loyaltyfy/webhooks",
"description": null,
"events": ["transaction.completed", "order.created"],
"status": "enabled",
"disabled_reason": null,
"livemode": false,
"created_at": "2026-10-06T18:17:32.381+00:00",
"secret": "whsec_UvjHvXfPk80ITYl_xPeAtskI0WjR8-PS"
}Что приходит
POST /loyaltyfy/webhooks HTTP/1.1
Content-Type: application/json
User-Agent: Loyaltyfy-Webhooks/1.0
Loyaltyfy-Signature: t=1791310488,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd{
"id": "evt_5a1f0c9e2b7d4e3f81a0c6d2",
"object": "event",
"type": "order.created",
"created": 1791310488,
"livemode": true,
"data": {
"object": {
"object": "order",
"id": "0c69a81c-0fbc-4333-b26c-789566b9bea4",
"number": 10,
"status": "new",
"table": "7",
...
}
}
}data.object это тот же объект, который возвращает API (транзакция, участник, заказ), в том состоянии, в каком он был в момент события. Ответьте любым 2xx в течение 10 секунд. Долгую обработку выполняйте после ответа.
Проверьте подпись
Loyaltyfy-Signature содержит метку времени t и подпись v1: HMAC-SHA256 от строки из t, точки и сырого тела запроса, с секретом вашего адреса в качестве ключа, в hex. Считайте подпись по тем байтам, которые получили, до разбора JSON, сравнивайте за постоянное время и отклоняйте метки времени старше пяти минут.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string or Buffer, not parsed JSON)
export function verifyLoyaltyfy(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hmac, hashlib, time
def verify_loyaltyfy(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Повторные доставки
Если ваш адрес не ответил 2xx вовремя, мы повторяем доставку через 1 минуту, 5 минут, 30 минут, 2 часа, 6 часов, 12 часов и 24 часа. После 20 неудачных доставок подряд адрес отключается, а заведение видит причину в кабинете. Включить его снова можно через PATCH /v1/webhook_endpoints/{id} с enabled: true.
Из-за повторов одно событие может прийти несколько раз, а события могут приходить не по порядку. Отсекайте дубликаты по id события, а что новее, определяйте по меткам времени самого объекта.
Проверка адреса
curl -X POST https://loyaltyfy.io/api/v1/webhook_endpoints/01c2a784-4bcf-43b9-8ac9-ddbad7a25341/test \
-H "Authorization: Bearer sk_test_..."
# { "object": "webhook_test", "delivered": true, "status_code": 200, "error": null }Полный список событий есть в справочнике событий.