Webhooks

locco dostarcza podpisane powiadomienia o zdarzeniach JSON do zarejestrowanych przez partnera endpointów HTTPS, gdy w organizacji wystąpią zdarzenia domenowe (utworzenie wpisu, zatwierdzenie wpisu, wypłata wpisu, aktualizacja wpisu, utworzenie lub aktualizacja pracownika). Ta strona stanowi dokumentację referencyjną dla partnerów: opisuje format sygnatury, mechanizm ochrony przed ponownym wysłaniem (replay protection), wymagania dotyczące idempotentności, politykę ponawiania prób oraz kanoniczny katalog typów zdarzeń.

Model push jest odpowiednikiem modelu pull dla API /api/v1/*. Payload zawiera te same obiekty DTO v1, które GET /api/v1/{resource}/{id} zwraca w odpowiedzi na zapytanie z kluczem API, dzięki czemu partnerzy korzystają z jednego kontraktu dla obu rozwiązań.

W skrócie

  • Partnerzy rejestrują endpoint HTTPS za pomocą POST /api/v1/webhooks i otrzymują jednorazowy klucz tajny do podpisywania (whsec_...).
  • locco wysyła żądania POST z formatem JSON na ten endpoint, gdy zostaną wywołane pasujące zdarzenia. Treści żądań (bodies) są podpisywane za pomocą HMAC-SHA256.
  • Nieudane doręczenia (błędy 5xx, błędy sieciowe, przekroczenia limitu czasu) są ponawiane z wykładniczym opóźnieniem (exponential back-off); błędy 4xx przerywają ponawianie. Po około 40 godzinach nieudanych prób (8 prób oprócz próby początkowej) doręczenie jest oznaczane jako wyczerpane (exhausted).
  • Po 10 kolejnych wyczerpanych doręczeniach subskrypcja jest automatycznie wyłączana. Aby ją reaktywować, należy zarejestrować ją ponownie.
  • Zagnieżdżony dziennik doręczeń (GET /api/v1/webhooks/{id}/deliveries) oraz strona w aplikacji Ustawienia → Integracje → Webhooks pokazują, co zostało wysłane i jaka odpowiedź została zwrócona.

Format sygnatury

Każde żądanie zawiera nagłówek Locco-Signature:

Locco-Signature: t=<unix-timestamp>,v1=<hex-hmac-sha256>

Dane wejściowe dla HMAC to kanoniczny ciąg znaków {timestamp}.{payload}:

  • {timestamp}: liczba całkowita w parametrze t= (sekundy Unix epoch, UTC).
  • .: dosłowny znak kropki.
  • {payload}: surowe, niezmodyfikowane bajty treści żądania. Nie należy ponownie serializować JSON przed obliczeniem. Białe znaki i kolejność kluczy mają znaczenie.

Format ten dokładnie odzwierciedla sygnaturę webhook Stripe (t=...,v1=...), więc partnerzy posiadający istniejący kod weryfikujący Stripe mogą go dostosować, zmieniając jedynie nazwę nagłówka.

Weryfikacja sygnatury

javascript
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 5 * 60; // 5 minut, zalecane przez Stripe
export function verifyLoccoSignature(rawBody, header, secret) {
// header wygląda następująco: "t=1714050000,v1=abc123..."
const parts = Object.fromEntries(
header.split(',').map((part) => part.split('=', 2)),
);
const timestamp = Number(parts.t);
const signature = parts.v1;
if (!timestamp || !signature) {
throw new Error('Malformed Locco-Signature header');
}
// 1. Ochrona przed ponownym wysłaniem: odrzuć wszystko, co jest starsze niż okno tolerancji.
const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - timestamp);
if (ageSeconds > TOLERANCE_SECONDS) {
throw new Error(`Signature timestamp is ${ageSeconds}s old; rejecting`);
}
// 2. Ponownie oblicz HMAC dla kanonicznych danych wejściowych.
const signedPayload = `${timestamp}.${rawBody}`;
const expected = createHmac('sha256', secret)
.update(signedPayload, 'utf8')
.digest('hex');
// 3. Porównanie w czasie stałym.
const expectedBuf = Buffer.from(expected, 'hex');
const actualBuf = Buffer.from(signature, 'hex');
if (
expectedBuf.length !== actualBuf.length ||
!timingSafeEqual(expectedBuf, actualBuf)
) {
throw new Error('Invalid signature');
}
return true;
}
python
import hmac, hashlib, time
TOLERANCE_SECONDS = 5 * 60
def verify_locco_signature(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts = int(parts["t"])
sig = parts["v1"]
if abs(time.time() - ts) > TOLERANCE_SECONDS:
raise ValueError("timestamp outside tolerance")
signed = f"{ts}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
raise ValueError("invalid signature")
return True
csharp
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public static bool VerifyLoccoSignature(ReadOnlySpan<byte> rawBody, string header, string secret)
{
const int toleranceSeconds = 5 * 60;
var parts = header.Split(',')
.Select(p => p.Split('=', 2))
.ToDictionary(p => p[0], p => p[1]);
var timestamp = long.Parse(parts["t"], CultureInfo.InvariantCulture);
var signature = parts["v1"];
var ageSeconds = Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - timestamp);
if (ageSeconds > toleranceSeconds) throw new InvalidOperationException("timestamp outside tolerance");
var signed = Encoding.UTF8.GetBytes($"{timestamp}.").Concat(rawBody.ToArray()).ToArray();
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = Convert.ToHexString(hmac.ComputeHash(signed)).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(signature));
}

Ten sam schemat działa w każdym języku: HMAC-SHA256 z {timestamp}.{payload} przy użyciu klucza tajnego (secret) jako klucza, zakodowany w formacie hex i porównany w czasie stałym z parametrem v1=.

Ochrona przed ponownym wysłaniem (WYMAGANE)

Partnerzy MUSZĄ odrzucać żądania, których timestamp jest starszy niż określone krótkie okno czasowe. Zalecana tolerancja wynosi 5 minut (zgodnie z domyślnym ustawieniem Stripe). Bez tej weryfikacji atakujący, który przechwyci prawidłową treść żądania i sygnaturę, może ją powtarzać w nieskończoność, dopóki klucz tajny nie zostanie zmieniony.

Należy odrzucić:

  • Brakujący lub nieprawidłowy nagłówek Locco-Signature.
  • Timestamp starszy niż okno tolerancji.
  • Timestamp znacząco wybiegający w przyszłość (należy zezwolić na niewielki margines przesunięcia zegara, np. 30 sekund).
  • Obliczony HMAC, który nie pasuje do v1=.

Do weryfikacji HMAC należy używać porównania w czasie stałym (timingSafeEqual w Node.js, hmac.compare_digest w Pythonie, CryptographicOperations.FixedTimeEquals w .NET). Zwykłe porównanie ciągów znaków (===) ujawnia informacje o czasie wykonania (timing leaks) i w teorii może pozwolić na odtworzenie sygnatury bajt po bajcie.

Idempotentność

Każde doręczenie zawiera nagłówek Locco-Event-Id, czyli stały identyfikator Guid identyfikujący zdarzenie logiczne. Ten sam identyfikator zdarzenia jest wysyłany ponownie w przypadku:

  • Ręcznych powtórzeń: partner kliknął Retry w interfejsie dziennika doręczeń lub wysłał żądanie POST na adres /api/v1/webhooks/{id}/deliveries/{deliveryId}/retry.
  • Ponownego doręczenia typu at-least-once: proces roboczy w tle, który ulegnie awarii między wysłaniem żądania a zarejestrowaniem sukcesu, ponownie zakolejuje ten sam wiersz przy następnym odpytywaniu.

Endpointy partnera MUSZĄ traktować powtarzające się identyfikatory Locco-Event-Id jako operacje bezskutkowe (no-ops). Standardowy wzorzec: utrzymywanie magazynu niedawnych doręczeń (Redis, mała tabela w bazie danych, pamięć podręczna w procesie) i odrzucanie każdego zdarzenia, którego identyfikator został już przetworzony w ciągu ostatniej godziny.

Bez tej deduplikacji efekt uboczny po stronie partnera (synchronizacja z ERP, wysłanie powiadomienia e-mail, modyfikacja rekordu) zostanie wykonany dwukrotnie dla tego samego zdarzenia logicznego przy każdej próbie ponowienia.

Semantyka ponawiania prób jest celowa:

  • Ponowna próba wysyła ten sam Locco-Event-Id, co pierwotne doręczenie. Partnerzy dokonują deduplikacji wyłącznie na podstawie nagłówka, bez konieczności analizowania payload.
  • Ponowna próba wysyła te same bajty treści, co pierwotne doręczenie. Sygnatura jest obliczana ponownie z nowym timestamp, ale dane wejściowe HMAC są identyczne, z wyjątkiem tego timestamp.

Polityka ponawiania prób

locco ponawia próby w przypadku przejściowych błędów z wykładniczym opóźnieniem (exponential back-off). Harmonogram obejmuje łącznie 9 prób HTTP (początkowe doręczenie plus 8 powtórzeń) rozłożonych w czasie na około 40 godzin.

PróbaOpóźnienie od poprzedniej próbyŁączny czas od 1. próby
1 (początkowa)(brak)0
230 sekund30s
31 minuta1m 30s
45 minut6m 30s
515 minut21m 30s
61 godzina~1h 21m
73 godziny~4h 21m
812 godzin~16h 21m
9 (ostatnia)24 godziny~40h 21m

Ponowienie a zakończenie

Odpowiedź partneraZachowanie
2xxSukces. Wiersz doręczenia oznaczony jako Succeeded. Brak ponownej próby.
4xx (z wyjątkiem 408, 429)Błąd po stronie partnera. Wiersz doręczenia oznaczony jako Failed. Brak ponownej próby.
408 (Request Timeout), 429 (Too Many)Traktowane jako błąd przejściowy, ponowienie zgodnie z harmonogramem.
5xxBłąd serwera. Ponowienie zgodnie z harmonogramem.
Błąd sieciowy (DNS, reset TCP, TLS handshake)Ponowienie zgodnie z harmonogramem.
Przekroczenie limitu czasu (locco rezygnuje po 10 sekundach)Ponowienie zgodnie z harmonogramem.

Po wyczerpaniu wszystkich 9 prób bez uzyskania statusu 2xx, wiersz doręczenia zostaje oznaczony jako Exhausted. Treść ostatniej odpowiedzi partnera (skrócona do 2 KB) jest zachowywana w dzienniku doręczeń, aby partnerzy mogli zdiagnozować, co zwracał ich endpoint.

Nagłówki Retry-After od partnerów nie są obsługiwane w wersji v1. Zawsze obowiązuje powyższy harmonogram.

Automatyczne wyłączenie

Jeśli subskrypcja zgromadzi 10 kolejnych doręczeń oznaczonych jako Exhausted, locco automatycznie ją wyłączy (IsActive = false, DisabledAt, DisabledReason). Chroni to locco przed ciągłym odpytywaniem trwale uszkodzonego endpointu.

Aby ponownie włączyć automatycznie wyłączoną subskrypcję, należy:

  1. Sprawdzić dziennik doręczeń, aby zidentyfikować, co zwracał endpoint partnera podczas awarii.
  2. Naprawić endpoint partnera.
  3. Usunąć subskrypcję i utworzyć nową (POST /api/v1/webhooks).

Typy zdarzeń

Katalog wersji v1. Nowe typy są dodawane poprzez zmiany w kodzie równolegle z nowymi zdarzeniami domenowymi, dzięki czemu partnerzy mogą mieć pewność, że ta lista stanowi kompletny zestaw dla każdego wydania.

Ten sam zestaw jest udostępniony w dokumentacji API jako schemat typu wyliczeniowego WebhookEventType. Generatory SDK tworzą typowany enum na podstawie tej listy, co zapewnia partnerom bezpieczeństwo typów na etapie kompilacji dla subskrybowanych wartości.

Typ zdarzeniaKiedy występujePayload data
entry.approvedWpis zmienił status na końcowy Approved w ramach kroku przepływu pracy (workflow). Wpisy zatwierdzane automatycznie (w firmach bez skonfigurowanego przepływu zatwierdzania) nie wywołują tego zdarzenia. Patrz entry.updated.Bezpieczne dla webhook podsumowanie wpisu (id + numer wpisu + id pracownika + daty + status + kwoty).
entry.paidoutWpis został oznaczony jako wypłacony w ramach paczki wypłat.Takie samo bezpieczne dla webhook podsumowanie wpisu.
entry.createdUtworzono nowy wpis.Takie samo bezpieczne dla webhook podsumowanie wpisu.
entry.updatedDziennik audytu wpisu zarejestrował zdarzenie inne niż utworzenie, końcowe zatwierdzenie lub wypłata, tzn. wpis uległ zmianie w sposób, który platforma uznaje za warty odnotowania. Obejmuje to edycję, przesłanie, pośrednie zatwierdzenia w przepływie pracy, anulowanie, zwrot do poprawy oraz zmiany encji podrzędnych (wydatki, diety, miejsca powstawania kosztów, załączniki).Takie samo bezpieczne dla webhook podsumowanie wpisu.
employee.createdUtworzono nowy rekord pracownika.Bezpieczne dla webhook podsumowanie pracownika (id + imię i nazwisko + stanowisko + status + znaczniki czasu audytu).
employee.updatedZaktualizowano istniejący rekord pracownika.Takie samo bezpieczne dla webhook podsumowanie pracownika.

Kontrakt payload webhook: zdarzenia zawierają identyfikatory, a nie dane osobowe (PII)

Treści zdarzeń webhook zawierają identyfikatory zasobów oraz minimalną migawkę prezentacyjną - nigdy dane osobowe (PII). W szczególności:

  • Zdarzenia dotyczące pracowników zawierają id, firstName, lastName, position, departmentId, status, isAccountOwner, isArchived oraz createdAt / editedAt. Nie zawierają one adresu e-mail, telefonu, adresu, identyfikatora podatkowego (OIB), konta bankowego (IBAN) ani kodu BIC.
  • Zdarzenia dotyczące wpisów zawierają identyfikator wpisu, numer wpisu, identyfikator pracownika, daty, status przepływu pracy, fazę przepływu pracy (phase), flagę wniosku o podróż (isRequest), status wypłaty, kwotę zaliczki w walucie wniosku i walucie bazowej oraz znaczniki czasu audytu. Nie zawierają one szczegółów dotyczących powiązanych wydatków, diet, miejsc powstawania kosztów, tekstu docelowego ani opisów tekstowych, raportów czy uwag dotyczących wstępnego zatwierdzenia.

isRequest ma wartość true, gdy podróż wciąż oczekuje na autoryzację (nieautoryzowany wniosek o podróż, a nie wystawiona delegacja); phase to numeryczna wartość WorkflowPhase - 1 (PreApproval), gdy podróż jest autoryzowana, 0 (Standard) dla zatwierdzenia rozliczenia po podróży oraz null, gdy wpis nie znajduje się obecnie w przepływie zatwierdzania. Podobnie jak status, phase jest serializowany w przesyłanym webhooku jako liczba całkowita, a nie ciąg znaków. Wniosek o podróż zawiera pusty entryNumber - kolejny numer jest nadawany dopiero po autoryzacji podróży. Zobacz sekcję Wnioski o podróż i odroczona numeracja poniżej, aby dowiedzieć się, jak te przejścia są prezentowane jako zdarzenia.

Gdy integracja partnera wymaga pełnych danych osobowych (PII) lub pełnych szczegółów zagnieżdżonych, pobiera zasób za pośrednictwem uwierzytelnionego API:

  • GET /api/v1/employees/{id} w celu pobrania pełnego rekordu pracownika (e-mail, telefon, adres, identyfikator podatkowy, konto bankowe).
  • GET /api/v1/travel-entries/{id} w celu pobrania pełnego wpisu (wydatki, diety, przypisania do miejsc powstawania kosztów, załączniki, pola tekstowe).

Jest to ten sam wzorzec, którego używa Stripe (treść zdarzenia = identyfikator; pełny zasób = uwierzytelnione żądanie GET). Żądania pull mogą być audytowane dla każdego wywołania pod kątem klucza API, który je wygenerował. Powiadomienia push webhook opuszczają locco w momencie ich wywołania i nie można ich przypisać w celach audytowych do konkretnego odbiorcy. Utrzymanie danych osobowych (PII) poza obszarem push ogranicza zakres RODO dla integracji partnera i pozwala uniknąć zmuszania endpointów partnera do obsługi danych osobowych na poziomie wejścia sieciowego, na co mogą nie być przygotowane.

Obiekty DTO webhooków w wersji v1 są typu append-only - mogą być dodawane nowe pola niezawierające danych osobowych (PII), natomiast istniejące pola nie będą usuwane ani zmieniane. Pola z danymi osobowymi (PII) nie zostaną dodane do payload webhooka - ten obszar stanowi kontrakt, a nie tylko domyślne zachowanie.

Wybór zdarzeń do subskrypcji

Dwa popularne modele integracji partnerskich:

  • Synchronizacja z ERP / systemem zewnętrznym: subskrypcja zdarzeń entry.created, entry.updated, entry.approved oraz entry.paidout. Razem zapewniają one pełny, jednokierunkowy podgląd cyklu życia każdego wpisu. W przypadku organizacji bez przepływu zatwierdzania zdarzenie entry.approved po prostu nigdy nie wystąpi (przypadek automatycznego zatwierdzenia przechodzi zamiast tego przez entry.updated). W przypadku organizacji z przepływem pracy zdarzenie entry.approved jest wymagane do przechwycenia ostatecznego przejścia w stan zatwierdzenia. W połączeniu z kontraktem deduplikacji opartym na Locco-Event-Id pozwala to na utrzymanie wiarygodnego zewnętrznego rejestru bez konieczności odpytywania (polling).
  • Przepływ pracy oparty na zatwierdzaniu: subskrypcja zdarzenia entry.approved, jeśli integracja dotyczy wyłącznie ostatecznego zatwierdzenia (np. uruchomienie procesu płatności w systemie zewnętrznym w momencie, gdy wpis zostanie w pełni zatwierdzony). Uwaga: zdarzenie entry.approved występuje tylko w organizacjach, które skonfigurowały przepływ zatwierdzania z co najmniej jednym krokiem posiadającym uprawnione osoby zatwierdzające. W przypadku organizacji bez przepływu pracy wpis jest automatycznie zatwierdzany przy przesłaniu i zamiast tego wywoływane jest zdarzenie entry.updated.

Wnioski o podróż i odroczona numeracja

Gdy dany typ wpisu korzysta z przepływu zatwierdzania podróży (wstępnego zatwierdzenia), podróż musi zostać autoryzowana, zanim stanie się rzeczywistą delegacją. W tym oknie czasowym wpis jest wnioskiem o podróż: isRequest ma wartość true, phase wynosi 1 (wartość PreApproval), a entryNumber jest pusty. Nie ma dedykowanego typu zdarzenia dla wniosków - cały cykl życia wniosku jest przekazywany za pośrednictwem entry.updated:

  • Przesłanie wniosku (podróż zostaje wysłana do autoryzacji): wywoływane jest zdarzenie entry.updated z isRequest: true, phase: 1 oraz pustym entryNumber.
  • Autoryzacja podróży (wniosek zostaje zatwierdzony i staje się edytowalną delegacją): wywoływane jest zdarzenie entry.updated z isRequest: false oraz uzupełnionym, nowo nadanym entryNumber. Jest to zdarzenie, które należy monitorować, jeśli rekordy w systemach zewnętrznych są powiązane z numerem wpisu - numer pojawia się tutaj, a nie w entry.created. Autoryzacja podróży nie wywołuje zdarzenia entry.approved - to zdarzenie jest zarezerwowane dla ostatecznego zatwierdzenia rozliczenia.
  • Odrzucenie lub anulowanie wniosku: wywoływane jest zdarzenie entry.updated z końcowym statusem. Odrzucony lub anulowany wniosek nigdy nie otrzymuje numeru wpisu.

Partnerzy korzystający z synchronizacji przyrostowej (delta-sync), którzy odpytują GET /api/v1/travel-entries?isRequest=false (oraz oczekują na niepusty entryNumber), zobaczą, że wpis trafia do ich zestawu delegacji w momencie autoryzacji podróży wywołującej entry.updated, a nie wcześniej.

Struktura koperty (envelope)

json
{
"id": "evt_b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7",
"type": "entry.approved",
"createdAt": "2026-04-23T12:34:56Z",
"data": {
"id": "...",
"entryNumber": "1/26",
"employeeId": "...",
"departureDate": "2026-04-21",
"returnDate": "2026-04-23",
"status": 4,
"phase": null,
"isRequest": false,
"advancePayment": 500.00,
"currencyCode": "EUR",
"advancePaymentInBaseCurrency": 500.00,
"createdAt": "2026-04-20T08:00:00Z",
"editedAt": "2026-04-23T12:34:56Z"
}
}
  • id: evt_ z następującymi po nim 32 znakami szesnastkowymi (bez myślników), np. evt_b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7. Semantycznie jest to ta sama wartość Guid, co w nagłówku Locco-Event-Id, ale w innym formacie ciągu znaków.
  • type: jeden z kanonicznych ciągów znaków typu zdarzenia wymienionych powyżej.
  • createdAt: znacznik czasu ISO 8601 UTC określający, kiedy zdarzenie powstało w locco (a nie kiedy zostało doręczone).
  • data: bezpieczne dla webhook podsumowanie zasobu zdarzenia (patrz sekcja „Kontrakt payload webhook” powyżej).

Struktura przesyłu jest zarejestrowana w dokumentacji API jako schemat WebhookEnvelope, gdzie pole data jest rozróżniane na podstawie type: zdarzenia entry.* zawierają obiekt TravelEntryWebhookDto, a zdarzenia employee.* zawierają obiekt EmployeeWebhookDto. Generatory SDK tworzą typowaną unię dyskryminowaną na podstawie dokumentu OpenAPI, dzięki czemu partnerzy korzystający z generatora otrzymują silnie typowany deserializator zamiast obiektu o dowolnej strukturze.

Uwaga dotycząca formatu: dopasowanie id do Locco-Event-Id. Pole id w kopercie ma format evt_{guid:N} (32 małe znaki szesnastkowe bez myślników), podczas gdy nagłówek Locco-Event-Id ma kanoniczną postać szesnastkową Guid 8-4-4-4-12 z myślnikami (np. b2c3d4e5-f6a7-b8c9-d0e1-f2a3b4c5d6e7). Reprezentują one tę samą wartość Guid, ale zwykłe porównanie ciągów znaków pod kątem równości zakończy się niepowodzeniem. Aby je porównać, należy: (a) usunąć prefiks evt_ i usunąć myślniki z wartości nagłówka przed porównaniem ciągów znaków, lub (b) sparsować obie wartości jako obiekty Guid i porównać sparsowane wartości. Formaty przesyłu są stabilne i celowe. Koperta webhooka Stripe używa tego samego skrótu evt_..., a partnerzy posiadający istniejący kod weryfikujący Stripe mogą przenieść ten wzorzec parsowania bez zmian.

Nagłówki

Każde żądanie POST webhooka zawiera:

HeaderOpis
Content-TypeZawsze application/json; charset=utf-8.
User-AgentLocco-Webhook/1.0. W razie potrzeby można na tej podstawie skonfigurować białe listy (allowlists) partnera.
Locco-EventCiąg znaków typu zdarzenia (np. entry.approved). Wygodne kierowanie (routing) bez konieczności analizowania treści żądania.
Locco-Event-IdStały identyfikator Guid na potrzeby idempotentności. Partnerzy MUSZĄ dokonywać deduplikacji na jego podstawie.
Locco-Signaturet={timestamp},v1={hex-hmac-sha256}. Patrz format sygnatury.
Locco-Delivery-IdWewnętrzny identyfikator wiersza doręczenia, przydatny przy korelacji z dziennikiem doręczeń za pośrednictwem wsparcia technicznego locco.

Rejestracja subskrypcji

Treść żądania POST /api/v1/webhooks:

json
{
"url": "https://partner.example.com/hooks/locco",
"eventTypes": ["entry.created", "entry.updated", "entry.approved", "entry.paidout"]
}

Ograniczenia:

  • url musi korzystać z protokołu HTTPS. Niezabezpieczony protokół HTTP jest odrzucany na etapie tworzenia oraz ponownie na etapie wysyłki w ramach wielopoziomowej ochrony (defense in depth).
  • url nie może wskazywać na localhost, loopback ani prywatne adresy IP RFC1918 (ochrona przed SSRF).
  • eventTypes nie może być pusty, a każda pozycja musi znajdować się w kanonicznym katalogu.

Treść odpowiedzi zwraca klucz tajny do podpisywania dokładnie raz jako signingSecret. Należy go natychmiast zapisać. Nie ma możliwości odzyskania utraconego klucza tajnego z kolejnego żądania. Klucz tajny nie jest zwracany przez GET /api/v1/webhooks ani GET /api/v1/webhooks/{id}.

Dziennik doręczeń

Można sprawdzić, co zostało wysłane i co zostało zwrócone, za pomocą:

  • W aplikacji: Ustawienia → Integracje → Webhooks (rozwijany wiersz dla każdego doręczenia, zawierający pełny payload i skróconą treść odpowiedzi).
  • API (zagnieżdżone pod subskrypcją nadrzędną):
    • GET /api/v1/webhooks/{id}/deliveries: lista doręczeń dla subskrypcji. Obsługuje parametry zapytania page, pageSize, status, eventType.
    • GET /api/v1/webhooks/{id}/deliveries/{deliveryId}: pobranie pojedynczego doręczenia, w tym pełnych podpisanych bajtów payload oraz skróconej treści odpowiedzi partnera. Zwraca status 404, jeśli doręczenie istnieje, ale należy do innej subskrypcji (próby dostępu między subskrypcjami są niedozwolone).

Ręczne ponowienie: POST /api/v1/webhooks/{id}/deliveries/{deliveryId}/retry. Dostępne tylko dla doręczeń o statusie Failed lub Exhausted. Ponowna próba zachowuje oryginalny Locco-Event-Id oraz bajty payload, dzięki czemu mechanizmy deduplikacji partnerów nadal działają poprawnie.

Retencja danych. Wiersze doręczeń - w tym zarejestrowane bajty payload - są trwale usuwane po 30 dniach przez codzienne zadanie czyszczące. Partnerzy, którzy wymagają stałego dziennika audytu każdego zdarzenia, muszą zapisać własną kopię w momencie odbioru. Endpoint dziennika doręczeń służy do krótkoterminowego monitorowania i ponawiania prób, a nie do długoterminowej archiwizacji. W połączeniu z projekcją payloadu webhooka pozbawioną danych osobowych (bajty payloadu zawierają identyfikatory zasobów i minimalną migawkę prezentacyjną, nigdy dane osobowe PII), historyczna weryfikacja za pośrednictwem tego endpointu nie ujawni informacji wykraczających poza identyfikatory zasobów.