1. Czym są Webhooki?
Webhooki to potężne narzędzie umożliwiające platformie Gridaly wysyłanie powiadomień w czasie rzeczywistym do Twoich zewnętrznych systemów za każdym razem, gdy w organizacji lub wydarzeniach zarządzanych w Gridaly zajdzie określone zdarzenie.
Zamiast ciągłego wysyłania zapytań do API Gridaly z pytaniem, czy pojawiły się nowe dane (tzw. polling), Gridaly automatycznie wyśle żądanie HTTP POST z odpowiednimi danymi pod wskazany przez Ciebie adres URL (tzw. webhook endpoint) natychmiast po wystąpieniu zdarzenia.
Typowe zastosowania:
Synchronizacja danych rejestracyjnych uczestników z systemem CRM.
Aktualizacja zewnętrznych baz danych w momencie sprzedaży biletu lub zameldowania (check-in) uczestnika.
Wyzwalanie niestandardowych scenariuszy (workflows) w narzędziach do automatyzacji marketingu.
Budowanie własnych pulpitów nawigacyjnych (dashboards) z danymi o wydarzeniu w czasie rzeczywistym.
Integracja z oprogramowaniem księgowym po wygenerowaniu faktury.
2. Tworzenie i konfiguracja Webhooka
Zarządzanie webhookami odbywa się w ustawieniach Organizacji w panelu administracyjnym Gridaly (Gridaly Admin).
Podczas tworzenia lub edycji webhooka należy skonfigurować następujące pola:
Name (Nazwa): Opisowa nazwa webhooka (np. „Synchronizacja CRM – Uczestnicy”).
Description (Opis - opcjonalnie): Dodatkowe informacje o przeznaczeniu webhooka.
URL: Adres URL punktu końcowego (HTTPS) Twojej zewnętrznej aplikacji, która będzie odbierać powiadomienia z Gridaly. Punkt końcowy musi być publicznie dostępny.
Active (Aktywny): Przełącznik włączający lub wyłączający webhook. Nieaktywne webhooki nie będą odbierać żadnych powiadomień.
Event Types (Typy zdarzeń): Wybór konkretnych zdarzeń w Gridaly, które mają wyzwalać ten webhook (np.
attendee.created,attendee.updated,attendee.deleted). Możesz zaznaczyć wiele typów zdarzeń. Pełna lista dostępna jest w sekcji 7.Events (Wydarzenia - opcjonalnie): Domyślnie webhook dotyczy wszystkich wydarzeń w ramach organizacji. Możesz ograniczyć jego działanie tylko do wyznaczonych wydarzeń.
Authentication Type (Typ uwierzytelniania): Metoda autoryzacji stosowana przez Gridaly przy wysyłaniu żądań:
None: Brak uwierzytelniania (zalecane tylko wtedy, gdy punkt końcowy jest zabezpieczony w inny sposób).
Header: Gridaly dołączy niestandardowy nagłówek HTTP.
Basic Auth: Gridaly użyje uwierzytelniania HTTP Basic Authentication.
Bearer Token: Gridaly dołączy nagłówek
Authorization: Bearer TWÓJ_TOKEN.Query String: Gridaly dołączy parametry uwierzytelniające bezpośrednio do adresu URL.
Authentication Token (Token uwierzytelniający): Sekret, token lub poświadczenia odpowiadające wybranemu typowi uwierzytelniania. Wymagany format zależy od wybranej metody:
Header: Wprowadź nazwę nagłówka i jego wartość rozdzielone dwukropkiem (
:), np.X-API-Key: twojaTajnaWartosc123.Basic Auth: Wprowadź nazwę użytkownika i hasło rozdzielone dwukropkiem (
:), np.uzytkownik:haslo.Bearer Token: Wprowadź samą wartość tokena, np.
twojaWartoscTokenaBearer.Query String: Wprowadź klucz i wartość parametru zapytania, np.
apiKey=twojTajnyKlucz(nie dołączaj znaków?ani&– Gridaly doda je automatycznie).
Secret Key (Klucz prywatny): Klucz generowany automatycznie przez Gridaly podczas tworzenia webhooka. Jest kluczowy do weryfikacji autentyczności przychodzących żądań. Należy go skopiować i bezpiecznie przechowywać w aplikacji odbierającej. Nie należy go nikomu udostępniać.
Retry Count (Liczba ponowień): Maksymalna liczba prób ponownego wysłania powiadomienia w przypadku niepowodzenia pierwotnej dostawy (np. z powodu tymczasowych problemów z siecią lub błędu serwera). Ponowne próby wykorzystują strategię wycofania wykładniczego (exponential backoff – wydłużanie odstępów czasowych między kolejnymi próbami).
3. Struktura ładunku danych (Payload)
W momencie wystąpienia wybranego zdarzenia Gridaly wysyła żądanie HTTP POST pod skonfigurowany adres URL z nagłówkiem Content-Type: application/json. Ciało żądania w formacie JSON posiada następującą strukturę:
JSON
{
"id": "123e4567-e89b-12d3-a456-426614174000",
// UUID of the primary resource related to the event
"event": "attendee.created",
// The type of event that occurred
"data": {
// Object containing detailed information about the event and resource
"id": "123e4567-e89b-12d3-a456-426614174000",
"firstname": "John",
"surname": "Doe",
"email": "john.doe@example.com"
// ... other relevant attendee fields
}
}
id: Unikalny identyfikator (UUID) głównego zasobu Gridaly powiązanego ze zdarzeniem (np. UUID uczestnika dla zdarzeniaattendee.created).event: Ciąg znaków określający typ zdarzenia, które wyzwoliło webhook (zgodny z subskrybowanymi typami).data: Obiekt zawierający szczegółowe dane dotyczące zdarzenia. Jego struktura zależy od typu zdarzenia.
4. Zabezpieczanie punktu końcowego (Weryfikacja podpisu)
Aby upewnić się, że przychodzące żądania rzeczywiście pochodzą z platformy Gridaly i nie zostały zmodyfikowane, należy zweryfikować podpis dołączony do każdego zapytania.
Gridaly dołącza do każdego żądania webhooka następujące nagłówki HTTP:
X-Gridaly-Signature: Podpis HMAC-SHA256 wygenerowany przy użyciu kluczaSecret Keyoraz surowej zawartości ładunku JSON (raw payload).X-Gridaly-Timestamp: Znacznik czasu Unixa (Unix timestamp) określający moment wysłania żądania.X-Gridaly-Event: Nazwa typu zdarzenia (np.attendee.created).X-Gridaly-Delivery: Unikalny identyfikator (UUID) konkretnej próby doręczenia.User-Agent: Zawsze ustawiony naGridaly-Webhooks/1.0.
Proces weryfikacji:
Pobranie nagłówków: Odczytaj wartości
X-Gridaly-SignatureorazX-Gridaly-Timestampz nagłówków przychodzącego żądania.Sprawdzenie znacznika czasu (opcjonalne, ale zalecane): Porównaj
X-Gridaly-Timestampz aktualnym czasem serwera. Odrzuć żądania zbyt stare (np. starsze niż 5 minut), aby zapobiec atakom typu replay attack.Pobranie surowego ładunku: Uzyskaj dostęp do surowej, niesparsowanej treści JSON żądania POST (
raw payload).Wygenerowanie podpisu: Oblicz skrót HMAC-SHA256 z surowego ładunku JSON, używając klucza
Secret Keyprzypisanego do danego webhooka w panelu Gridaly.Porównanie podpisów: Porównaj podpis obliczony w kroku 4 z podpisem odebranym w nagłówku
X-Gridaly-Signature. Jeśli to możliwe, użyj funkcji porównywania odpornej na ataki czasowe (timing-safe comparison).Akceptacja lub odrzucenie: Jeżeli podpisy są zgodne (a znacznik czasu mieści się w dopuszczalnym przedziale), żądanie jest autentyczne i można je przetworzyć. W przeciwnym razie odrzuć żądanie (zwracając kod błędu HTTP 400 lub 401) i zarejestruj zdarzenie w logach.
Przykład kodu (pseudokod):
JavaScript
// Your webhook endpoint handler function
function handleGridalyWebhook(request) {
const receivedSignature = request.headers['X-Gridaly-Signature'];
const receivedTimestamp = request.headers['X-Gridaly-Timestamp'];
const rawPayload = request.rawBody; // Access the raw request body
// 1. Get your stored Secret Key for this webhook
const secretKey = getSecretKeyFromYourConfig();
// 2. (Optional) Check timestamp tolerance
const tolerance = 5 * 60; // 5 minutes in seconds
const currentTimestamp = Math.floor(Date.now() / 1000);
if (Math.abs(currentTimestamp - receivedTimestamp) > tolerance) {
console.error("Timestamp validation failed");
return response.status(400).send("Timestamp validation failed");
}
// 3. Compute the expected signature
const expectedSignature = computeHmacSha256(rawPayload, secretKey);
// 4. Compare signatures securely
if (secureCompare(receivedSignature, expectedSignature)) {
// Signatures match - process the payload
const payload = JSON.parse(rawPayload);
processEvent(payload);
return response.status(200).send("OK");
} else {
// Signatures do NOT match - reject the request
console.error("Signature validation failed");
return response.status(401).send("Signature validation failed");
}
}
// Helper function for HMAC-SHA256
// (implementation depends on your language/framework)
function computeHmacSha256(data, secret) {
// ... use crypto library to compute HMAC-SHA256 hash ...
return computedHash;
}
// Helper function for timing-safe comparison
// (implementation depends on your language/framework)
function secureCompare(a, b) {
// ... implement timing-safe string comparison ...
return areEqual;
}
5. Monitorowanie doręczeń
Gridaly prowadzi dziennik wszystkich prób doręczenia powiadomień. Logi te są dostępne w sekcji konfiguracji Webhooków.
Tabela dziennika doręczeń (Delivery Logs) zawiera:
Event Type (Typ zdarzenia): Zdarzenie, które wyzwoliło próbę doręczenia.
Status: Wynik próby doręczenia:
Pending (Oczekujące): Doręczenie zaplanowane, próba jeszcze się nie odbyła.
Success (Sukces): Punkt końcowy odebrał żądanie i zwrócił kod statusu HTTP z zakresu 2xx.
Failed (Niepowodzenie): Próba zakończona błędem (np. błąd sieci, przekroczenie czasu oczekiwania lub kod statusu inny niż 2xx).
Response Code (Kod odpowiedzi): Kod statusu HTTP zwrócony przez Twój punkt końcowy (jeśli odpowiedź nadeszła).
Attempt (Próba): Numer próby dla danego powiadomienia (1 dla pierwszej próby, 2 dla pierwszego ponowienia itd.).
Processed At (Czas przetworzenia): Data i godzina zakończenia próby doręczenia.
Regularna analiza dzienników jest kluczowa do diagnozowania problemów. W przypadku powtarzających się błędów należy zweryfikować dostępność punktu końcowego, przejrzeć logi serwera oraz upewnić się, że prawidłowo zwraca on kody z zakresu 2xx po pomyślnym przetworzeniu żądania.
6. Rozwiązywanie problemów (Troubleshooting)
Notoryczne błędy doręczeń:
Sprawdź kody odpowiedzi i komunikaty błędów w dzienniku doręczeń (Delivery Logs).
Upewnij się, że podany adres URL jest poprawny i publicznie dostępny.
Sprawdź, czy serwer/aplikacja działa prawidłowo i nie zgłasza błędów w logach wewnętrznych.
Zweryfikuj ustawienia reguł zapory sieciowej (firewall) lub grup bezpieczeństwa, które mogą blokować ruch z serwerów Gridaly.
Upewnij się, że punkt końcowy odpowiada kodem z zakresu 2xx w odpowiednim czasie (domyślny limit czasu oczekiwania w Gridaly wynosi około 10 sekund).
Błąd weryfikacji podpisu:
Upewnij się, że używasz właściwego klucza
Secret Keyskopiowanego z panelu Gridaly.Upewnij się, że skrót HMAC-SHA256 jest obliczany na surowej treści żądania (raw request body), a nie na obiekcie już przetworzonym/sparsowanym.
Brak przychodzących zdarzeń:
Upewnij się, że webhook ma status Active.
Sprawdź, czy zasubskrybowano właściwe Event Types.
Jeśli stosujesz filtrowanie wydarzeń, upewnij się, że zdarzenie występuje w ramach wybranego wydarzenia.
7. Dostępne zdarzenia Webhooków
7.1. Zdarzenia dotyczące Uczestników (Attendee Events)
Typ zdarzenia | Wyzwalacz | Opis |
| Utworzenie nowego uczestnika | Wysyłane w momencie zarejestrowania nowego uczestnika na wydarzenie |
| Modyfikacja danych uczestnika | Wysyłane po aktualizacji danych (profil, status, ustawienia itp.) |
| Usunięcie uczestnika | Wysyłane po usunięciu uczestnika z systemu |
| Zameldowanie uczestnika (check-in) | Wysyłane w momencie zameldowania uczestnika na wydarzeniu (używa odrębnego, skróconego ładunku danych) |
Struktura ładunku dla danych Uczestnika:
Obiekt data dla zdarzeń attendee.created, attendee.updated oraz attendee.deleted:
JSON
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "john.doe@example.com",
"additionalEmail": "john.alternate@example.com",
"activity": "active",
"status": "confirmed",
"active": true,
"public": true,
"virtualPerson": false,
"phone": "+1234567890",
"order": 1,
"lang": "en",
"country": "US",
"externalId": "EXT-12345",
"externalTicket": "TKT-98765",
"firstname": "John",
"surname": "Doe",
"headline": "Software Engineer",
"pemail": "john.profile@example.com",
"website": "https://johndoe.com",
"pphone": "+1234567890",
"biography": "Experienced software engineer...",
"location": "New York, USA",
"company": "Tech Corp",
"position": "Senior Developer",
"timezone": "America/New_York",
"activedAt": "2024-01-15T10:30:00Z",
"checkinAt": "2024-01-20T09:00:00Z",
"firstAt": "2024-01-15T10:35:00Z",
"activityWebAt": "2024-01-15T10:30:00Z",
"activityMobileAt": "2024-01-16T08:00:00Z",
"activityWebApp": "active",
"activityMobileApp": "active",
"disableTransmissionReactions": false,
"gamificationShowInRanking": true,
"gamificationNotifyEndedTask": true,
"meetingsNotificationsViaEmail": true,
"groupsNotificationsViaEmail": true,
"doNotDisturb": false,
"receiveMeetingProposals": true,
"autoAcceptMeetingInvitations": false,
"autoCreateExhibitorScanForLead": true,
"pushNotifications": true,
"canBeVoted": true,
"headlinePattern": "default",
"showOnNetworkingList": true,
"marketingConsent": true,
"avatar": "https://example.com/avatars/small/john.jpg",
"ticket": {
"id": "ticket-uuid",
"name": "VIP Pass"
},
"addonables": [],
"exhibitors": [],
"roles": [
{
"id": "role-uuid",
"name": "Speaker"
}
],
"tags": [
{
"id": "tag-uuid",
"name": "Technology"
}
],
"linkedin": "https://linkedin.com/in/johndoe",
"twitter": "@johndoe"
}
Struktura ładunku dla zameldowania Uczestnika (Check-in):
Zdarzenie attendee.checkin przesyła dedykowany, skrócony obiekt data:
JSON
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "john.doe@example.com",
"firstname": "John",
"surname": "Doe",
"ticket": {
"id": "ticket-uuid",
"name": "VIP Pass"
},
"checkinAt": "2024-01-20T09:00:00Z",
"checkinBy": "Anna Kowalska",
"createdAt": "2024-01-15T10:30:00Z"
}
7.2. Zdarzenia dotyczące Zamówień (Order Events)
Typ zdarzenia | Wyzwalacz | Opis |
| Złożenie nowego zamówienia | Wysyłane po utworzeniu nowego zamówienia w systemie |
| Modyfikacja zamówienia | Wysyłane po aktualizacji danych zamówienia (status, dane kupującego, dane do faktury itp.) |
| Usunięcie zamówienia | Wysyłane po usunięciu zamówienia z systemu |
Struktura ładunku dla Zamówień:
Obiekt data dla zdarzeń dotyczących zamówień:
JSON
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"eid": "ORD-2024-001234",
"externalId": "EXT-ORD-5678",
"status": "completed",
"firstname": "Jane",
"surname": "Smith",
"email": "jane.smith@example.com",
"customFields": {
"dietary_preferences": "vegetarian",
"company_size": "50-100"
},
"invoiceType": "company",
"invoiceFirstname": "Jane",
"invoiceSurname": "Smith",
"invoiceCompany": "Smith & Co",
"invoiceTaxId": "1234567890",
"invoiceStreet": "Main Street",
"invoiceHouse": "123",
"invoiceApartment": "4B",
"invoicePostalCode": "10001",
"invoiceCity": "New York",
"invoiceCountry": "US",
"language": "en",
"timezone": "America/New_York",
"eventId": "event-uuid",
"events": [
"event-uuid-1",
"event-uuid-2"
],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T11:00:00Z"
}
7.3. Zdarzenia dotyczące Płatności (Payment Events)
Typ zdarzenia | Wyzwalacz | Opis |
| Inicjalizacja płatności | Wysyłane po utworzeniu nowego rekordu płatności |
| Zmiana statusu/danych płatności | Wysyłane po aktualizacji płatności (zmiana statusu, zatwierdzenie, odrzucenie itp.) |
| Usunięcie płatności | Wysyłane po usunięciu płatności z systemu |
Struktura ładunku dla Płatności:
Obiekt data dla zdarzeń dotyczących płatności:
JSON
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"eid": "PAY-2024-001234",
"number": "INV/2024/001234",
"kind": "invoice",
"externalId": "STRIPE-ch_1234567890",
"amount": 15000,
"currency": "USD",
"paymentStatus": "paid",
"paymentMethod": "card",
"paymentProvider": "stripe",
"paymentDays": 14,
"paymentItems": [
{
"name": "VIP Pass",
"type": "ticket",
"quantity": 2,
"netTotal": 12195.12,
"vatTotal": 2804.88,
"grossTotal": 15000
}
],
"ticketsQuantity": 2,
"addonsQuantity": 0,
"invoiceType": "company",
"invoiceFirstname": "Jane",
"invoiceSurname": "Smith",
"invoiceCompany": "Smith & Co",
"invoiceTaxId": "1234567890",
"invoiceStreet": "Main Street",
"invoiceHouse": "123",
"invoiceApartment": "4B",
"invoicePostalCode": "10001",
"invoiceCity": "New York",
"invoiceCountry": "US",
"invoiceAlert": null,
"vatFree": false,
"commentBuyer": "Please send invoice to accounting department",
"commentOrganiser": "VIP client - priority processing",
"serviceRealized": true,
"voucherId": "voucher-uuid",
"orderId": "order-uuid",
"eventId": "event-uuid",
"events": [
"event-uuid-1",
"event-uuid-2"
],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T11:00:00Z",
"expiredAt": "2024-01-29T23:59:59Z",
"approvedAt": "2024-01-15T11:00:00Z",
"rejectedAt": null
}
7.4. Zdarzenia dotyczące Dokumentów Księgowych (Accounting Document Events)
Gridaly przesyła również webhooki dla dokumentów księgowych wystawianych w systemie: zaliczek, korekt, faktur, proform oraz paragonów. Wszystkie typy dokumentów dzielą wspólną bazową strukturę ładunku, rozszerzoną o pola specyficzne dla danego typu.
Typ zdarzenia | Wyzwalacz | Opis |
| Utworzenie faktury zaliczkowej | Wysyłane po wystawieniu faktury zaliczkowej |
| Modyfikacja faktury zaliczkowej | Wysyłane po zmianie danych zaliczki (status, numeracja, płatność itp.) |
| Utworzenie korekty | Wysyłane po utworzeniu korekty faktury, zaliczki lub paragonu |
| Modyfikacja korekty | Wysyłane po zmianie danych dokumentu korekty |
| Utworzenie faktury | Wysyłane po wystawieniu nowej faktury |
| Modyfikacja faktury | Wysyłane po zmianie danych faktury (status, numeracja, płatność itp.) |
| Utworzenie proformy | Wysyłane po wystawieniu proformy |
| Modyfikacja proformy | Wysyłane po zmianie danych proformy |
| Utworzenie paragonu | Wysyłane po wystawieniu paragonu |
| Modyfikacja paragonu | Wysyłane po zmianie danych paragonu |
Bazowa struktura ładunku dla Dokumentów Księgowych:
Obiekt data dla wszystkich zdarzeń związanych z dokumentami księgowymi zawiera następujące pola podstawowe:
JSON
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"eid": "INV-2024-001234",
"number": "FV/01/2024/0001",
"status": 2,
"url": "https://app.gridaly.com/...",
"netTotal": 12195.12,
"vatTotal": 2804.88,
"grossTotal": 15000,
"currency": "USD",
"languages": [
"en",
"pl"
],
"alert": null,
"vatFree": false,
"vatExemption": null,
"NBPExchangeRate": null,
"items": [
{
"name": "VIP Pass",
"type": "ticket",
"quantity": 2,
"netTotal": 12195.12,
"vatTotal": 2804.88,
"grossTotal": 15000
}
],
"ticketsQuantity": 2,
"addonsQuantity": 0,
"buyerType": "company",
"buyerFirstname": "Jane",
"buyerSurname": "Smith",
"buyerCompany": "Smith & Co",
"buyerTaxId": "1234567890",
"buyerStreet": "Main Street",
"buyerHouse": "123",
"buyerApartment": "4B",
"buyerPostalCode": "10001",
"buyerCity": "New York",
"buyerCountry": "US",
"sellerCompany": "Event Organizer Ltd",
"sellerTaxId": "0987654321",
"system": "gridaly",
"integration": null,
"orderId": "order-uuid",
"paymentId": "payment-uuid",
"eventId": "event-uuid",
"events": [
"event-uuid-1",
"event-uuid-2"
],
"invoiceAt": "2024-01-15T10:30:00Z",
"exchangeAt": null,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T11:00:00Z"
}
Struktura obiektu integration zależy od zewnętrznego systemu księgowego, w którym dokument został wystawiony:
Dla Fakturowni oraz inFaktu:
{ "id": "external-document-id" }Dla KSeF:
{ "number": "ksef-number", "referenceNumber": "ksef-reference-number" }Dla Gridaly:
null(dokument wystawiony bezpośrednio w Gridaly)
Pola specyficzne dla poszczególnych typów dokumentów:
Oprócz pól bazowych, poszczególne typy dokumentów zawierają dodatkowo:
Faktura (
invoice.*):paymentStatus,paymentDays,advances(identyfikatory UUID zaliczek rozliczonych fakturą),soldAt,paidAt.Zaliczka (
advance.*):paymentStatus,paymentDays,corrections(identyfikatory UUID korekt danej zaliczki),issuePlannedAt,soldAt,paidAt.Paragon (
receipt.*):paymentStatus,paymentDays,advances(identyfikatory UUID zaliczek rozliczonych paragonem),soldAt,paidAt.Proforma (
proforma.*):paymentStatus,paymentDays.Korekta (
correction.*):kind(valueCorrectionlubquantitiveCorrection),correctableIdorazcorrectableType(UUID oraz typ korygowanego dokumentu:invoice,advancelubreceipt),correctionId(UUID nowszej korekty zastępującej obecną, jeśli występuje),refundId(UUID powiązanego zwrotu środków, jeśli występuje),soldAt,dueAt.
8. Dobre praktyki (Best Practices)
Stosuj protokół HTTPS: Adresy URL punktów końcowych powinny zawsze używać protokołu
https://, aby zapewnić szyfrowanie danych w transmisji.Weryfikuj podpisy: Zawsze weryfikuj nagłówek
X-Gridaly-Signaturew celu potwierdzenia autentyczności i spójności danych.Odpowiadaj niezwłocznie: Potwierdzaj odebranie webhooka, zwracając kod statusu HTTP z zakresu 2xx tak szybko, jak to możliwe (optymalnie w ciągu 2–3 sekund). Złożone operacje logiczne wykonuj asynchronicznie (np. przy użyciu kolejek zadań w tle), aby uniknąć przekroczenia limitu czasu odpowiedzi (timeout).
Obsługuj powtórzenia (Idempotentność): Z powodu mechanizmu ponowień punkt końcowy może odebrać to samo powiadomienie wielokrotnie. Zaprojektuj logikę przetwarzania w sposób idempotentny (tak, aby wielokrotne przetworzenie tego samego powiadomienia nie powodowało niepożądanych skutków ubocznych). Do identyfikacji dubli wykorzystaj nagłówek
X-Gridaly-Delivery(unikalny dla każdej próby) lub poleidz ładunku danych.Monitoruj punkt końcowy: Prowadź stały monitoring logów i wydajności aplikacji, aby upewnić się, że jest ona w stanie obsłużyć bieżący wolumen ruchu wygenerowanego przez webhooki.