Przejdź do treści

Webhooki — przebieg integracji

Webhooki to jedyny kanał, którym informujemy partnera o zdarzeniach powstałych po naszej stronie. Wszystko pozostałe w API jest odpowiedzią na zapytanie partnera.

Po co są

Część zdarzeń nie wynika z działania użytkownika: przypomnienie o zabiegu wystawia nasz automat, odpowiedź weterynarza na czacie przychodzi w dowolnym momencie, ankieta kontrolna po wizycie powstaje dwa dni po niej. W aplikacji PetAssist sygnalizują je mail, powiadomienie w aplikacji i push — użytkownikom integracji nie wysyłamy żadnego z nich, zgodnie z ustaleniem, że komunikacja z użytkownikiem należy do partnera.

Webhook zastępuje więc te trzy kanały. Bez jego obsługi część funkcji pozostanie niewidoczna dla użytkownika, dopóki sam nie otworzy właściwego ekranu.

Konfiguracja

Po stronie partnera potrzebny jest jeden adres HTTPS przyjmujący POST. Adres i sekret ustawiamy my, przy integracji — nie ma endpointu do samodzielnej zmiany.

Zdarzenia

Ładunek niesie wyłącznie identyfikatory i czas. Nie wysyłamy tytułów ani treści powiadomień: brzmienie komunikatu należy do partnera, tak samo jak decyzja, czy w ogóle je pokazać.

Zdarzenie Kiedy data
chat.message.received odpowiedź weterynarza (albo wiadomość automatyczna) w konwersacji użytkownika conversationId
pet.cyclic_treatment.reminder termin czynności pielęgnacyjnej petCyclicTreatmentId, notificationId
pet.external_appointment.reminder termin zaplanowanej wizyty zewnętrznej externalAppointmentId, petId
pet.treatment.reminder termin badania, szczepienia, odrobaczania lub profilaktyki externalAppointmentId, petId, cyclicTreatmentId
visit.monitoring.created powstała ankieta kontrolna po wizycie u weterynarza z naszej sieci monitoringId, visitId, petId, count

Zdarzenie czatu dotyczy wyłącznie wiadomości przychodzących — własna wiadomość użytkownika, wysłana przez POST /chat/{conversationId}/messages, żadnego webhooka nie wywołuje.

Leki nie mają dziś własnego zdarzenia — przypomnienia o nich widać na osi czasu.

Ostatnie zdarzenie dotyczy wyłącznie wizyt zakładanych przez naszych weterynarzy; wizyty wpisywane przez właściciela żadnej ankiety nie generują. Skąd biorą się jedne i drugie, opisuje dokumentacja wizyt.

Co zrobić z każdym z nich

Zdarzenie Dokąd po szczegóły
chat.message.received GET /chat/{conversationId}/messages?take=30
pet.cyclic_treatment.reminder GET /pets/cyclic-treatment-reminders/{reminderId}
pet.external_appointment.reminder GET /pets/{petId}/external-appointments/{externalAppointmentId}
pet.treatment.reminder jak wyżej — zabieg przychodzi razem z wizytą
visit.monitoring.created GET /med-visit/monitoring/{monitoringId}

Zwróć uwagę, że przy pielęgnacji notificationId to identyfikator przypomnienia — i to jego wstawia się w ścieżkę jako {reminderId}. Identyfikator samej czynności przychodzi obok, jako petCyclicTreatmentId, i służy do jej edycji albo oznaczenia wykonania. Ten sam podział opisuje dokumentacja pielęgnacji.

Format żądania

POST https://…                      (adres partnera)
content-type: application/json
x-webhook-event: pet.treatment.reminder
x-webhook-id: 8f14e45f-ce15-4f3a-9a2b-1d5f2b7c0e11
x-webhook-signature: 9a1c…            (HMAC-SHA256, hex)
{ "eventId": "8f14e45f-ce15-4f3a-9a2b-1d5f2b7c0e11",
  "type": "pet.treatment.reminder",
  "userId": "…",
  "occurredAt": "2026-09-10T14:30:00.000Z",
  "data": { "externalAppointmentId": "…", "petId": "…", "cyclicTreatmentId": "…" } }

userId to identyfikator użytkownika w tej integracji — ten sam, który partner wysyła w nagłówku x-user-id. Po nim rozstrzyga się, kogo dotyczy zdarzenie.

Weryfikacja podpisu

x-webhook-signature to HMAC-SHA256 z surowego ciała żądania, kluczem jest sekret integracji, wynik zapisany szesnastkowo. Liczy się bajty ciała przed parsowaniem JSON — ponowne serializowanie sparsowanego obiektu da inny podpis.

const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(rawBody)
    .digest('hex');

const ok = crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(req.headers['x-webhook-signature']),
);

Porównanie warto wykonać funkcją odporną na pomiar czasu, jak wyżej.

Odpowiedź i ponawianie

Oczekujemy statusu 2xx. Wszystko inne, tak samo jak brak odpowiedzi w ciągu 10 sekund, uznajemy za niepowodzenie i ponawiamy:

Próba Odstęp od poprzedniej
2 1 minuta
3 5 minut
4 15 minut
5 1 godzina
6 3 godziny
7 8 godzin

Po siedmiu nieudanych próbach doręczenie oznaczamy jako nieudane i więcej nie ponawiamy. Tak samo kończą zdarzenia czekające w kolejce, jeśli w międzyczasie webhooki zostaną dla integracji wyłączone — nie są wtedy doręczane ani przechowywane do później. Kolejka jest obsługiwana co minutę, więc pierwsze doręczenie następuje do minuty od zdarzenia.

Idempotencja

eventId (równe nagłówkowi x-webhook-id) jest stałe dla wszystkich ponowień tego samego zdarzenia. Należy go zapisywać i pomijać ładunki już przetworzone — inaczej przy powtórce powstaną zduplikowane powiadomienia u użytkownika.

Odpowiadaj szybko

Doręczenie liczy się jako nieudane po 10 sekundach, a odpowiadanie po tym czasie powoduje ponowienia. Zalecamy przyjąć ładunek, odpowiedzieć 200 i dopiero potem odpytywać nasze API o szczegóły.

Najczęstsze przyczyny błędów

Objaw Przyczyna
Podpis się nie zgadza Policzono go z ponownie zserializowanego JSON-a zamiast z surowego ciała
Brak nagłówka x-webhook-signature Integracja nie ma ustawionego sekretu — prosimy o kontakt
Zdarzenia przychodzą wielokrotnie Odpowiedź inna niż 2xx albo wolniejsza niż 10 s; sprawdź eventId przed przetworzeniem
Zduplikowane powiadomienia u użytkownika Brak sprawdzania eventId
Nic nie przychodzi Adres nieustawiony albo webhooki wyłączone po naszej stronie
Przychodzi zdarzenie czatu, choć weterynarz nic nie napisał To wiadomość automatyczna — z punktu widzenia użytkownika jest zwykłą wiadomością w rozmowie

Zalecany przebieg po stronie odbiorcy

1. weryfikacja x-webhook-signature na surowym ciele
2. sprawdzenie, czy eventId nie był już przetworzony
3. odpowiedź 200
4. odpytanie naszego API o szczegóły, zależnie od type
5. wyświetlenie powiadomienia użytkownikowi z userId