Analiza
Podpisane przesyłki i e-mail, który nic nie zdradza.
Webhook wysyła każdą nową odpowiedź na wybrany przez ciebie adres https.
Każda przesyłka niesie nagłówek X-NumoForms-Signature:
sha256=, a po nim HMAC-SHA256 dokładnej treści żądania,
wyliczony z sekretem właściwym dla danego webhooka, dzięki czemu twój
punkt końcowy może udowodnić, że ładunek pochodzi od nas. Niezależnie od
tego wybrane adresy e-mail mogą otrzymywać powiadomienie o napłynięciu
odpowiedzi — a te wiadomości celowo nie zawierają żadnych odpowiedzi.
Co trafia do twojego punktu końcowego
Jedno żądanie POST z JSON-em na odpowiedź. Treść zawiera pięć pól i nic poza tym, więc parser da się napisać bez zgadywania.
| Pole | Co zawiera |
|---|---|
event | response.created |
survey_id | Ankieta, do której należy odpowiedź. |
response_id | Unikalny identyfikator tej odpowiedzi. |
created_at | Moment zarejestrowania odpowiedzi. |
answers | Odpowiedzi, kluczowane identyfikatorem pytania. |
| Nagłówek | Do czego służy |
|---|---|
X-NumoForms-Event | Nazwa zdarzenia. Dziś jest jedno: response.created. |
X-NumoForms-Signature | sha256= a po nim HMAC-SHA256 dokładnej treści żądania, zapisany szesnastkowo. |
X-NumoForms-Delivery | Unikalny identyfikator tej próby dostarczenia, dzięki któremu rozpoznasz duplikat. |
Dostarczenie uruchamia wyzwalacz bazodanowy na tabeli odpowiedzi, a nie konkretny ekran aplikacji. Ma to większe znaczenie, niż brzmi: odpowiedź przesłana z formularza osadzonego, z linku hostowanego albo odpowiedź częściowa wznowiona po kilku dniach przez zapisz i wróć później przechodzą tą samą drogą, więc nie ma trasy, o której podpięciu zapomnielibyśmy.
Webhook uruchamia się w chwili, gdy odpowiedź wpływa. Jeśli włączyłeś zatwierdzanie, nie czeka on na decyzję o zatwierdzeniu albo odrzuceniu i po jej podjęciu nie wysyła drugiej przesyłki. Traktuj przychodzący ładunek jako „odpowiedź istnieje”, a nie „odpowiedź została zaakceptowana”.
Dlaczego podpis nie jest opcjonalny
Adres URL webhooka to sekret, który wycieka. Trafia do zgłoszenia w systemie ticketowym, do wiadomości na Slacku, na zrzut ekranu w dokumencie przekazania obowiązków, do historii przeglądarki na współdzielonym laptopie. Bez podpisu każdy, kto zobaczył ten adres, może wysłać na niego, co zechce, a twoje systemy zaewidencjonują to jako prawdziwą odpowiedź w konsultacjach. Dla samorządu liczącego sprzeciwy wobec wniosku o pozwolenie na budowę nie jest to problem teoretyczny.
Dlatego każda przesyłka jest podpisana. Każdy webhook dostaje własny, losowo wygenerowany sekret, pokazywany w kreatorze pod przyciskiem wyświetlania sekretu podpisującego. Obliczamy HMAC-SHA256 z dokładnych bajtów treści żądania przy użyciu tego sekretu; ty przeliczasz to ponownie ze swoją kopią i porównujesz. Sfałszowane żądanie nie przejdzie, bo fałszerz nie ma sekretu.
Adres musi być https. Wymusza to ograniczenie w bazie
danych, a nie podpowiedź w formularzu, ponieważ sekret podpisujący
wędrujący nieszyfrowanym połączeniem nie jest sekretem.
Weryfikacja przesyłki w Node.js
W praktyce najwięcej szkód robią dwa szczegóły. Używaj surowej treści żądania, a nie obiektu sparsowanego i ponownie zserializowanego — różnice w białych znakach zmieniają skrót. I porównuj w stałym czasie, aby atakujący nie mógł poznać poprawnego podpisu bajt po bajcie na podstawie czasów odpowiedzi.
import { createHmac, timingSafeEqual } from 'node:crypto';
const SECRET = process.env.NOISSIME_WEBHOOK_SECRET;
// rawBody must be the exact bytes received, before any JSON parsing.
// Re-serialising a parsed object changes the whitespace and the check fails.
function isFromNoissime(rawBody, header) {
if (typeof header !== 'string') return false;
const expected =
'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header);
// timingSafeEqual throws on a length mismatch, so compare lengths first.
return a.length === b.length && timingSafeEqual(a, b);
}
Wywołaj to, zanim cokolwiek sparsujesz. Jeśli zwróci fałsz, odpowiedz kodem 401 i odrzuć żądanie. Jeśli zwróci prawdę, masz odpowiedź, która mogła pochodzić wyłącznie z twojej ankiety.
Dziennik dostarczeń
Webhook, który po cichu przestał działać, wygląda dokładnie tak samo jak taki, którego nigdy nie wywołano: nic się nie dzieje i nic ci o tym nie mówi. Dlatego każda próba jest zapisywana w dzienniku dostarczeń, a ostatni status HTTP widnieje przy webhooku w kreatorze — zielony przy 2xx, czerwony przy czymkolwiek innym.
Dostarczanie jest asynchroniczne, więc status jest uzgadniany po fakcie, a nie zgadywany w chwili wysyłki. Widzisz status, który faktycznie zwrócił twój serwer. Każdy webhook można wstrzymać i wznowić bez usuwania go, co zwykle jest tym, czego potrzebujesz w trakcie przewdrażania systemu odbierającego.
Jedno świadome ograniczenie: nie ma ponawiania z wykładniczo narastającym opóźnieniem. Dostarczenie jest próbowane raz, wynik zostaje zapisany i na tym koniec. Zbudowanie kolejki ponowień, która szanuje kolejność i idempotencję, to prawdziwa praca, a deklarowanie czegoś, czego nie zbudowaliśmy, byłoby gorsze niż powiedzenie tego wprost. W międzyczasie odpowiedź nigdy nie ginie — jest w bazie danych, widoczna w analityce i raportowaniu oraz możliwa do wyeksportowania do pliku CSV.
Wadliwy webhook nie może też zepsuć zbierania danych. Jeśli dostarczanie zgłosi błąd, błąd zostaje zapisany, a odpowiedź i tak jest rejestrowana. Liczy się odpowiedź respondenta; dostarczenie działa na zasadzie najlepszych starań.
Powiadomienia e-mail, bez odpowiedzi w treści
Dodaj adresy, które mają wiedzieć o nowych odpowiedziach, a otrzymają e-mail, gdy któraś wpłynie. Wiadomość ma postać firmowego HTML-a z alternatywą w czystym tekście, ponieważ i klienty tekstowe, i filtry antyspamowe czytają część tekstową. Podaje nazwę ankiety, bieżącą sumę odpowiedzi i odsyła do wyników.
Nie zawiera natomiast odpowiedzi. To decyzja, a nie przeoczenie. E-mail z powiadomieniem jest przekazywany koledze, automatycznie odkładany do wspólnej skrzynki, synchronizowany z telefonem i przechowywany w kopii zapasowej przez lata. Wrzucenie do tego łańcucha otwartych odpowiedzi z konsultacji rozsypuje dane osobowe po systemach, których nikt nie ocenił, i po cichu unieważnia kontrolę dostępu opisaną na stronie o bezpieczeństwie. Dane respondentów zostają w bazie danych, a e-mail mówi ci, żebyś tam zajrzał.
Podobnie jak webhooki, powiadomienia uruchamia wyzwalacz bazodanowy, więc idą za odpowiedzią, a nie za konkretną drogą jej przesłania. Jeśli nie skonfigurowano żadnych adresów, nic nie jest wysyłane ani nawet próbowane.
Czego to nie robi
- Brak aplikacji Zapiera i brak łącznika do Arkuszy Google.
- Brak ponawiania z narastającym opóźnieniem — jedna próba na odpowiedź, a potem dziennik.
- Brak zdarzeń wychodzących innych niż
response.created. Zatwierdzenia, edycje i usunięcia nie uruchamiają webhooków. - Brak przychodzącego API do programowego tworzenia ankiet albo odczytu odpowiedzi.
- Brak listy dozwolonych adresów IP dla przesyłek. Uwierzytelnieniem jest podpis.
Częste pytania
Jak zweryfikować nagłówek X-NumoForms-Signature?
Oblicz HMAC-SHA256 surowej treści żądania przy użyciu sekretu podpisującego danego webhooka, zakoduj go szesnastkowo, poprzedź ciągiem „sha256=” i porównaj z nagłówkiem za pomocą porównania o stałym czasie wykonania. Weryfikuj przed sparsowaniem JSON-a i odrzucaj kodem 401 wszystko, co się nie zgadza.
Co się stanie, jeśli mój punkt końcowy będzie niedostępny w chwili napłynięcia odpowiedzi?
Dostarczenie jest próbowane jeden raz, a wynik zapisywany w dzienniku dostarczeń wraz ze statusem HTTP. Nie ma automatycznego ponawiania z narastającym opóźnieniem. Sama odpowiedź nigdy nie jest zagrożona: niepowodzenie webhooka nie może przeszkodzić w zapisaniu odpowiedzi, a dane zawsze odzyskasz z ekranu wyników albo z eksportu CSV.
Czy webhook uruchamia się dla odpowiedzi przesłanej z formularza osadzonego?
Tak. Dostarczenie uruchamia wyzwalacz bazodanowy na tabeli odpowiedzi, a nie konkretny ekran aplikacji, więc dzieje się to niezależnie od tego, skąd pochodzi odpowiedź — z linku hostowanego, z formularza osadzonego czy z odpowiedzi częściowej właśnie dokończonej po wznowieniu.
Czy mogę wysłać webhooka na adres http://?
Nie. Ograniczenie kontrolne w bazie danych wymaga, by adres URL zaczynał się od https://, a kreator odmawia przyjęcia czegokolwiek innego. Sekret podpisujący przesłany nieszyfrowanym połączeniem nie jest sekretem.
Czy e-maile z powiadomieniami zawierają odpowiedzi respondenta?
Nie, i jest to celowe. E-mail informuje, że wpłynęła odpowiedź, podaje nazwę ankiety i bieżącą sumę oraz odsyła do wyników. Powiadomienia bywają przekazywane dalej i leżą w kopiach zapasowych skrzynek pocztowych, więc dane respondentów zostają w bazie danych, tam gdzie działa kontrola dostępu.
Czy istnieje aplikacja Zapiera albo integracja z Arkuszami Google?
Nie. Webhooki są dziś jedyną integracją wychodzącą. Nie ma aplikacji Zapiera ani łącznika do Arkuszy Google i dla żadnego z nich nie ogłoszono terminu.
Wepnij ankietę we własne systemy.
Skieruj webhooka na punkt końcowy https, a każda odpowiedź trafi tam podpisana, więc odbiorca może udowodnić, że pochodzi od nas, a nie od kogoś, kto znalazł adres. Dodaj adresy, które mają dostać e-mail, gdy wpłynie odpowiedź, a same odpowiedzi zostaną w bazie danych, tam gdzie działa kontrola dostępu.
- Podpis HMAC-SHA256 przy każdej przesyłce
- Dziennik dostarczeń pokazujący ostatni status HTTP
- Uruchamiany przez wyzwalacz bazodanowy, niezależnie od drogi wejścia