Преминете към основното съдържание

Webhooks: събития към вашата интеграция

Webhook позволява на външна система, например CRM или собствено приложение, да научава за промяна в Reservation.Studio в момента, в който стане, вместо да пита за нея отново и отново. Когато се случи събитие, например нова резервация, смяна на час или нов клиент, Reservation.Studio изпраща кратко съобщение на адрес, който вие сте посочили.

Страницата е за две аудитории
  • Собственик или мениджър: разделите от „Преди да започнете“ до „Кога адресът се спира“ описват какво правите в Business приложението.
  • Разработчик на интеграцията: от „За разработчика: какво получава сървърът“ нататък са заглавките, тялото, подписът и повторните опити.

Преди да започнете​

  • Абонаментът трябва да включва "Външна свързаност". Ако абонаментът престане да я включва, събития не се изпращат.
  • Служителят, който настройва адресите, има нужните права: "Преглед на webhook адреси", "Създаване на webhook адреси", "Редактиране, тест и повторно изпращане на webhook адреси" и "Изтриване на webhook адреси".
  • Нужен е адрес, който разработчикът на интеграцията ви е дал. Той започва с https:// и е достъпен от интернет.

Как да добавите адрес​

  1. Отворете Интеграции.
  2. В картата "Webhooks" изберете "Настройки".
  3. Изберете "Нов webhook адрес".
  4. Попълнете полетата (виж по-долу) и изберете поне едно събитие.
  5. Изберете "Създай".
  6. Копирайте тайната от прозореца "Тайна на 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-timestampUnix време в секунди на този опит.
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=…, са полезни за собствена защита на приемника, ако не проверявате подписа. Те не се виждат в историята на изпращанията.

Свързани теми​

💡Все още нямаш бизнес профил?

За да използвате тези функции в Business, е нужен бизнес профил. Създайте профил и продължете с настройката.