Webhooks: събития към вашата интеграция
Webhook позволява на външна система, например CRM или собствено приложение, да научава за промяна в Reservation.Studio в момента, в който стане, вместо да пита за нея отново и отново. Когато се случи събитие, например нова резервация, смяна на час или нов клиент, Reservation.Studio изпраща кратко съобщение на адрес, който вие сте посочили.
- Собственик или мениджър: разделите от „Преди да започнете“ до „Кога адресът се спира“ описват какво правите в Business приложението.
- Разработчик на интеграцията: от „За разработчика: какво получава сървърът“ нататък са заглавките, тялото, подписът и повторните опити.
Преди да започнете
- Абонаментът трябва да включва "Външна свързаност". Ако абонаментът престане да я включва, събития не се изпращат.
- Служителят, който настройва адресите, има нужните права: "Преглед на webhook адреси", "Създаване на webhook адреси", "Редактиране, тест и повторно изпращане на webhook адреси" и "Изтриване на webhook адреси".
- Нужен е адрес, който разработчикът на интеграцията ви е дал. Той започва с
https://и е достъпен от интернет.
Как да добавите адрес
- Отворете Интеграции.
- В картата "Webhooks" изберете "Настройки".
- Изберете "Нов webhook адрес".
- Попълнете полетата (виж по-долу) и изберете поне едно събитие.
- Изберете "Създай".
- Копирайте тайната от прозореца "Тайна на webhook" и я предайте на разработчика на интеграцията. С нея интеграцията проверява, че събитията идват от Reservation.Studio.
След като запазите, адресът се появява в списъка и започва да получава избраните събития.
След затваряне на прозореца не можете да видите тайната отново. Който я има, може да праща фалшиви събития към вашия сървър, затова не я споделяйте с никого. Ако я изгубите, генерирайте нова (виж „Какво правите от менюто на реда“).
Какво означава всяко поле
- "Име" е вашето описание на адреса, например името на интеграцията.
- "Адрес (URL)" е сървърът на интеграцията, който получава събитията. Приемат се само
https://адреси, достъпни от интернет. Адрес, който сочи към вътрешна мрежа, се отказва. - "Имейл за известия" е имейлът, на който идва известие, когато адресът бъде спрян, защото сървърът продължава да се проваля. Обикновено е на разработчика на интеграцията. Ако полето е празно, известието получава служителят, който е създал адреса.
- "Активен" включва или спира адреса. Спрян адрес нито получава, нито пази събития. Повторното му включване не изпраща пропуснатото.
- "Събития" са събитията, които искате да получавате. Бутонът "Всички" отбелязва всички събития в групата, а "Премахни избрани" ги маха. Нужно е поне едно събитие.
Кои събития се изпращат
| Група | Събитие | Кога се изпраща |
|---|---|---|
| Резервации | "Резервацията е създадена" (appointment.created) | Нова резервация или занимание, от който и да е канал. |
| Резервации | "Резервацията е променена" (appointment.updated) | Нов час, обект, услуги или служители. |
| Резервации | "Резервацията е изтрита" (appointment.deleted) | Резервацията е махната от календара. |
| Резервации | "Резервацията е възстановена" (appointment.restored) | Изтрита резервация е отново в календара. |
| Участници | "Клиент се записа" (appointment-client.created) | Клиент се записа в съществуваща резервация или занимание. |
| Участници | "Статусът на клиента е сменен" (appointment-client.status-changed) | Потвърден, отказан, неявил се или приключил. |
| Участници | "Клиент е махнат" (appointment-client.deleted) | Клиент е махнат от резервация. |
| Клиенти | "Клиентът е създаден" (client.created) | Нов клиентски профил. |
| Клиенти | "Клиентът е променен" (client.updated) | Изпращат се имената на променените полета, никога стойностите им. |
Събитията съобщават какво се е случило и за кой запис, а не носят всички данни. Интеграцията чете пълния запис от API с API ключ, за който сте дали нужните права.
- Масовият импорт на резервации и клиенти не изпраща събития.
- Сливането на два клиента не изпраща събития. Ако интеграцията ви синхронизира клиенти, сверявайте я периодично със списъка с клиенти.
- Резервация, в която клиентът е записан още при създаването ѝ, изпраща само "Резервацията е създадена", без отделно "Клиент се записа".
- Изтрита резервация не изпраща събития за участниците си.
Какво виждате в списъка
Списъкът показва за всеки адрес "Име" и адреса, броя "Събития", "Статус" и "Последна успешна доставка" (или "Никога").
Статусът е един от:
- "Активен": адресът получава събитията си.
- "Изключен": вие сте го спрели с превключвателя "Активен".
- "Проваля се от": сървърът не приема събитията и ние още опитваме. Виждате от кога.
- "Спрян: проваля се от 3 дни": адресът се е провалял непрекъснато 3 дни и е спрян автоматично.
- "Спрян: сървърът отговори с 410 Gone": сървърът е отговорил, че адресът вече не съществува, и е спрян веднага.
Какво правите от менюто на реда
Менюто на реда има четири действия:
- "Редактирай" отваря формата на адреса. За да включите отново спрян адрес, включете "Активен" и запазете.
- "Изпрати тестово събитие" изпраща тестово събитие на запазения адрес и показва в съобщение какво е отговорил сървърът, например „Сървърът отговори с 200 за 142 ms.“ или защо не е стигнало. Тестът не се брои към автоматичното спиране.
- "Генерирай нова тайна" създава нова тайна. Сегашната спира веднага, затова сървърът на интеграцията има нужда от новата, преди да приема събития. Новата тайна се показва веднъж, като при създаването.
- "Изтрий" премахва адреса. Изтрит адрес не се възстановява: за да го върнете, създайте нов.
История на изпращанията
Бутонът "История на изпращанията" в горната част на екрана отваря справката за изпращанията, стеснена до Webhooks. Всяко изпращане е ред, а при избор на ред се отваря "Подробности за изпращането": "HTTP отговор", "Опити", "Следващ опит" и "Вид проблем". Тестовите събития са отбелязани като "Тестово".
Когато събитие не е стигнало до сървъра, в подробностите виждате бутон "Изпрати отново". Той е наличен за служители с право "Редактиране, тест и повторно изпращане на webhook адреси". След изпращане виждате съобщението „Събитието тръгна отново.“
Кога адресът се спира
- Веднага, когато сървърът отговори с
410 Gone. - След 3 дни, ако всяко изпращане към него се проваля. Статусът става "Спрян: проваля се от 3 дни", а на "Имейл за известия" идва писмо.
Докато адресът е спрян, събития за него не се пазят. Когато го включите отново, пропуснатото не се изпраща. Интеграцията го наваксва със заявки към API, например като прочете резервациите за периода (виж по-долу).
За разработчика: какво получава сървърът
Тази част е за разработчика на интеграцията. Подписът следва спецификацията Standard Webhooks, затова за повечето езици има готови библиотеки.
За всяко събитие изпращаме POST с JSON тяло и тези заглавки:
POST https://адрес-на-интеграцията
Content-Type: application/json
User-Agent: ReservationStudio-Webhooks/1
webhook-id: evt_01JB2K8Z3M4N5P6Q7R8S9T0V1W
webhook-timestamp: 1791882842
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
| Заглавка | Съдържание |
|---|---|
webhook-id | Идентификатор на събитието. Еднакъв е при всеки повторен опит, затова по него отсявате дубликатите. |
webhook-timestamp | Unix време в секунди на този опит. |
webhook-signature | Подписът: v1, и base64 стойност. |
Тялото има една и съща обвивка за всички събития:
{
"id": "evt_01JB2K8Z3M4N5P6Q7R8S9T0V1W",
"type": "appointment.updated",
"timestamp": "2026-10-13T09:14:02+00:00",
"business_id": "812",
"actor": { "type": "employee", "id": "4410" },
"data": {
"type": "appointments",
"id": "55120",
"location_id": "1904",
"start_at": "2026-10-15 14:00:00",
"end_at": "2026-10-15 15:00:00",
"changed": ["time"]
}
}
| Поле | Какво означава |
|---|---|
id | Същият като webhook-id. |
type | Типът на събитието, виж таблицата по-горе. |
timestamp | Кога е станала промяната. webhook-timestamp е кога е опитът за изпращане. |
business_id | Бизнесът, в който е станало събитието. |
actor | Кой е причинил промяната. actor.type е employee, client, api-key или system. actor.id е идентификаторът на служителя или клиента в бизнеса, или на API ключа. При system е null, както и при служител или клиент без профил в бизнеса, например поддръжката. |
data | Кратки данни за събитието, виж по-долу. |
По actor интеграция, която пише през API ключ, разпознава собствените си промени и не влиза в цикъл. Редът на полетата в тялото не е гарантиран.
Данните на всяко събитие
Тялото е кратко: носи тип на записа, идентификатор и няколко факта. start_at и end_at са стенен час във времевата зона на бизнеса. Всички идентификатори са низове. Пълният запис четете с GET:
- резервация:
GET /v2/appointments/{data.id}; - клиент:
GET /v2/clients/{data.id}; - участник (
appointment-clients) няма собственGET, четете го през резервацията му:GET /v2/appointments/{data.appointment_id}?include=clients.
Заявките се изпращат с API ключ, който има правата за съответния ресурс.
appointment.created и appointment.restored
{
"type": "appointments",
"id": "55120",
"location_id": "1904",
"start_at": "2026-10-15 14:00:00",
"end_at": "2026-10-15 15:00:00"
}
start_at и end_at са null, когато резервацията няма час.
appointment.updated
{
"type": "appointments",
"id": "55120",
"location_id": "1904",
"start_at": "2026-10-15 14:00:00",
"end_at": "2026-10-15 15:00:00",
"changed": ["time"]
}
changed е списък от time, location и services. Смяната на служителите се отчита като services.
appointment.deleted
{
"type": "appointments",
"id": "55120",
"location_id": "1904"
}
appointment-client.created
{
"type": "appointment-clients",
"id": "90417",
"appointment_id": "55120",
"client_id": "7730",
"status_id": "3"
}
status_id е идентификатор на статус от GET /v2/appointment-statuses.
appointment-client.status-changed
{
"type": "appointment-clients",
"id": "90417",
"appointment_id": "55120",
"client_id": "7730",
"status_id": "5",
"previous_status_id": "3"
}
appointment-client.deleted
{
"type": "appointment-clients",
"id": "90417",
"appointment_id": "55120",
"client_id": "7730"
}
client.created
{
"type": "clients",
"id": "7730"
}
client.updated
{
"type": "clients",
"id": "7730",
"changed": ["city", "phoneCountry"]
}
changed съдържа имената на променените полета, както са в GET /v2/clients/{id}, никога стойностите им. Членството в групи не се отчита.
webhook.test
{
"endpoint_id": "26"
}
Това е тестовото събитие от "Изпрати тестово събитие".
Проверка на подписа
Подписът е HMAC-SHA256 върху {webhook-id}.{webhook-timestamp}.{тяло}. Ключът са декодираните от base64 байтове на тайната след префикса whsec_. Проверявайте суровото тяло, преди да го разчетете като JSON.
Node.js с библиотеката standardwebhooks:
import { Webhook } from 'standardwebhooks';
const payload = new Webhook(process.env.WEBHOOK_SECRET).verify(rawBody, request.headers);
PHP без библиотека:
$key = base64_decode(substr($secret, strlen('whsec_')));
$expected = 'v1,' . base64_encode(hash_hmac('sha256', "$id.$timestamp.$rawBody", $key, true));
$valid = abs(time() - (int) $timestamp) <= 300
&& in_array(true, array_map(fn ($signature) => hash_equals($expected, $signature), explode(' ', $signatureHeader)), true);
Отказвайте заявки, чийто webhook-timestamp е на повече от 5 минути от текущия час.
Отговор, повторни опити и гаранции
- Успех е всеки отговор
2xxдо 15 секунди. Пренасочванията (3xx) не се следват и са неуспех. - Отговаряйте бързо и обработвайте събитието асинхронно.
- Неуспешна доставка се опитва общо 9 пъти за около 75 часа: веднага, след 1 мин, 5 мин, 30 мин, 2 ч, 6 ч, 12 ч, 24 ч и 30 ч, с до 10% случайно отклонение.
- Гаранцията е поне веднъж, без гарантиран ред. Две доставки на едно и също събитие са възможни, а по-късно събитие може да пристигне преди по-ранно. Отсявайте дубликатите по
idи четете текущото състояние сGET. - За събитията от времето, когато адресът е бил спрян, наваксвайте със списъчните заявки, например
GET /v2/appointmentsза период.
Изисквания към адреса
- Само
https, до 2048 знака, без потребител и парола в адреса. - Домейнът се пише с ASCII знаци. Кирилски домейн се въвежда във формата
xn--: вместохуки.бгсе пишеxn--h1ad0ah.xn--90ae. - Хостът трябва да сочи само към публични адреси. Проверяваме го при запис и при всеки опит. Хост, който в момента не се намира, е обикновен неуспех и се повтаря. Хост, който сочи към вътрешна мрежа, се отказва без повторен опит и се брои към 3-дневното спиране.
- Параметрите в адреса, например
?token=…, са полезни за собствена защита на приемника, ако не проверявате подписа. Те не се виждат в историята на изпращанията.