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.

PoleCo zawiera
eventresponse.created
survey_idAnkieta, do której należy odpowiedź.
response_idUnikalny identyfikator tej odpowiedzi.
created_atMoment zarejestrowania odpowiedzi.
answersOdpowiedzi, kluczowane identyfikatorem pytania.
NagłówekDo czego służy
X-NumoForms-EventNazwa zdarzenia. Dziś jest jedno: response.created.
X-NumoForms-Signaturesha256= a po nim HMAC-SHA256 dokładnej treści żądania, zapisany szesnastkowo.
X-NumoForms-DeliveryUnikalny 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

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
Zacznij tworzyć