Restoza API и вебхуки
API позволяет вашим системам (CRM, касса, сайт) читать брони и столы, создавать и менять брони. Вебхуки — обратное направление: Restoza сама присылает событие на ваш адрес, когда бронь создана или изменена. Ключи и адреса вебхуков создаются администратором заведения в «Настройках» → «Интеграции».
Авторизация
Передавайте ключ в заголовке Authorization: Bearer <ключ>. Ключ привязан к одному заведению и показывается один раз при создании. Ключ «только чтение» не может создавать и менять брони.
curl https://restoza.ru/api/v1/tables \
-H "Authorization: Bearer rz_xxxxxxxxxxxxxxxx"Лимит — 60 запросов в минуту на ключ; при превышении — ответ 429 с заголовком Retry-After. Если доступ заведения к платформе ограничен — 403 access_blocked.
Ошибки
{ "error": { "code": "conflict", "message": "Один из выбранных столов уже занят на это время" } }400—validation_error,invalid_date,invalid_statusи т. п.401—missing_api_key,invalid_api_key(неверный или отозванный)403—read_only_key,access_blocked404—not_found409—conflict(стол занят другой бронью)429—rate_limited
Объект брони
{
"id": 123,
"status": "CONFIRMED", // PENDING | CONFIRMED | REJECTED | WAITLIST
"guestName": "Иван",
"phone": "+7 999 000-00-00",
"email": null, // необязательный; если указан — гость получает письма о статусе
"guestsCount": 4,
"comment": "У окна", // пожелание гостя
"adminNote": null, // внутренняя заметка персонала
"startAt": "2026-10-10T15:00:00.000Z",
"endAt": "2026-10-10T17:00:00.000Z",
"arrivedAt": null, // время отметки «гость пришёл»
"noShowAt": null, // время отметки «гость не пришёл» (неявка); не бывает одновременно с arrivedAt
"depositPaid": false,
"source": "WEB", // откуда заявка: WEB | TELEGRAM | MAX | ADMIN (создана в кабинете) | API
"cancelledByGuestAt": null, // время отмены гостем в мини-приложении (статус при этом REJECTED)
"preferredHall": null, // зал, выбранный гостем в форме: { "id": 2, "name": "Терраса" } или null
"createdAt": "2026-10-02T10:00:00.000Z",
"updatedAt": "2026-10-02T10:00:00.000Z",
"tables": [{ "id": 1, "label": "1", "number": 1, "zone": "BAR", "capacityMin": 1, "capacityMax": 2 }],
"services": [ // услуги брони (подтягиваются по столу или добавлены вручную); суммы — в рублях
{ "id": 1, "name": "Депозит", "kind": "DEPOSIT", "priceType": "FIXED", "unitPrice": 5000, "auto": true, "paidAt": null }
],
"guestTags": ["аллергия на орехи"],
"guestNotes": null
}Время — в UTC (ISO 8601). Статусы: PENDING — новая заявка, CONFIRMED — подтверждена, со столом, WAITLIST — лист ожидания (без стола), REJECTED — отклонена/отменена.
Методы
GET/bookings
from, to (по времени начала, ISO 8601), status (через запятую), updatedSince (для синхронизации — только изменённые после момента), limit (1–500, по умолчанию 100), cursor. Ответ: { "data": [...], "nextCursor": 456 } — пока nextCursor не null, передавайте его в cursor для следующей страницы.curl "https://restoza.ru/api/v1/bookings?from=2026-10-10T00:00:00Z&to=2026-10-11T00:00:00Z&status=CONFIRMED" \
-H "Authorization: Bearer rz_xxx"GET/bookings/{id}
POST/bookings
startAt, durationHours (1–6), guestName, phone, guestsCount — обязательные; tableIds, email, comment, depositPaid, arrived — по желанию. Со столами бронь сразу CONFIRMED, без столов — WAITLIST. Время должно укладываться в часы работы заведения. Если указан email, гость получит письмо (подтверждение или «заявка получена»), пока письма не отключены в настройках заведения.curl -X POST https://restoza.ru/api/v1/bookings \
-H "Authorization: Bearer rz_xxx" -H "Content-Type: application/json" \
-d '{"startAt":"2026-10-10T15:00:00Z","durationHours":2,"guestName":"Иван",
"phone":"+79990000000","guestsCount":4,"tableIds":[1]}'PATCH/bookings/{id}
status, tableIds (полная замена списка столов), startAt, endAt, guestName, phone, email (пустая строка или null — очистить), guestsCount, comment, adminNote, arrived (true/false), noShow (true/false; только для подтверждённой брони после её начала), depositPaid. Отмена брони — {"status":"REJECTED"}.GET/tables
GET/services
kind: DEPOSIT, ENTRY или EXTRA; priceType: FIXED, PER_GUEST или PER_HOUR; цена в рублях) и столами, к которым они привязаны.GET/availability
start (ISO 8601) и durationHours. Ответ: { "data": { "tables": [{ "id": 1, "status": "free" }] } }.Вебхуки
События:
booking.created— бронь создана (форма на сайте, панель, API)booking.updated— бронь изменена; вdata.changes— список изменённых полейbooking.status_changed— сменился статус; вdata.previousStatus— прежний
Если при изменении поменялся статус, вы получите оба события: booking.updated и booking.status_changed. В «Настройках» → «Интеграции» можно подписаться только на нужные.
POST /ваш-адрес
Content-Type: application/json
X-Restoza-Event: booking.created
X-Restoza-Delivery: 981
X-Restoza-Timestamp: 1790000000
X-Restoza-Signature: sha256=3b1f…
{
"id": "evt_9f2c1d7a5b3e8c4d6a1f0e2b",
"event": "booking.created",
"createdAt": "2026-10-02T10:00:00.000Z",
"data": { "booking": { …объект брони… } }
}Отвечайте любым 2xx в течение 10 секунд. Поле id события уникально — используйте его, чтобы не обработать одно событие дважды (при повторах оно то же самое).
Проверка подписи
Подпись — HMAC-SHA256 от строки <timestamp>.<тело запроса> с вашим секретом (он виден в «Настройках» → «Интеграции»). Сравнивайте в постоянное время и отбрасывайте запросы с устаревшим timestamp (например, старше 5 минут) — так старый перехваченный запрос нельзя отправить повторно. Подписывайте и проверяйте именно сырое тело, до разбора JSON.
Node.js:
const crypto = require("crypto");
function verify(secret, rawBody, headers) {
const ts = headers["x-restoza-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = "sha256=" +
crypto.createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex");
const given = headers["x-restoza-signature"] || "";
return given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Python:
import hmac, hashlib, time
def verify(secret: str, raw_body: bytes, headers) -> bool:
ts = headers.get("X-Restoza-Timestamp", "")
if abs(time.time() - int(ts)) > 300:
return False
digest = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest("sha256=" + digest, headers.get("X-Restoza-Signature", ""))PHP:
function verify(string $secret, string $rawBody, array $headers): bool {
$ts = $headers['X-Restoza-Timestamp'] ?? '';
if (abs(time() - (int)$ts) > 300) return false;
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
return hash_equals($expected, $headers['X-Restoza-Signature'] ?? '');
}Повторы и отключение
Если ваш сервер не ответил 2xx, отправка повторяется: через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов (всего до 6 попыток). После этого доставка помечается неудачной — её можно отправить вручную из журнала. Если подряд накопится много неудач, адрес отключается автоматически (исправьте приёмник и включите его снова). Адреса во внутренней сети (localhost, 10.x, 192.168.x и т. п.) не допускаются; редиректы не выполняются.