Analiză
Livrări semnate și un e-mail care nu spune nimic.
Un webhook trimite fiecare răspuns nou către un URL https ales de
dumneavoastră. Fiecare livrare poartă un antet
X-NumoForms-Signature: sha256= urmat de un
HMAC-SHA256 al corpului exact al cererii, calculat cu un secret unic
pentru acel webhook, astfel încât punctul dumneavoastră final să poată
dovedi că sarcina utilă a venit de la noi. Separat, adresele de e-mail
alese pot fi anunțate la sosirea unui răspuns — iar acele e-mailuri nu
conțin, în mod deliberat, niciun răspuns.
Ce ajunge la punctul dumneavoastră final
Un singur POST JSON pentru fiecare răspuns. Corpul conține cinci câmpuri și nimic altceva, așa că puteți scrie un parser fără să ghiciți.
| Câmp | Ce conține |
|---|---|
event | response.created |
survey_id | Chestionarul căruia îi aparține răspunsul. |
response_id | Identificatorul unic al acestui răspuns. |
created_at | Momentul în care a fost înregistrat răspunsul. |
answers | Răspunsurile, indexate după identificatorul întrebării. |
| Antet | La ce folosește |
|---|---|
X-NumoForms-Event | Numele evenimentului. Astăzi există unul singur: response.created. |
X-NumoForms-Signature | sha256= urmat de HMAC-SHA256 al corpului exact al cererii, în hexazecimal. |
X-NumoForms-Delivery | Un identificator unic pentru această încercare de livrare, ca să puteți depista un duplicat. |
Livrarea este declanșată de un declanșator din baza de date, aplicat tabelului de răspunsuri, nu de un anumit ecran al aplicației. Asta contează mai mult decât pare: un răspuns trimis printr-un formular integrat, printr-un link găzduit sau un răspuns parțial reluat peste câteva zile prin salvare și continuare parcurg toate aceeași cale, deci nu există vreo rută pe care am uitat să o conectăm.
Webhookul se declanșează în momentul în care sosește răspunsul. Dacă ați activat aprobările, el nu așteaptă decizia de aprobare sau de respingere și nu se trimite o a doua livrare atunci când decizia este luată. Tratați o sarcină utilă primită ca pe „există un răspuns”, nu ca pe „un răspuns a fost aprobat”.
De ce semnătura nu este opțională
Un URL de webhook este un secret care se scurge. Ajunge într-un tichet, într-un mesaj de Slack, într-o captură de ecran dintr-un document de predare-primire, în istoricul unui browser de pe un laptop folosit în comun. Fără semnătură, oricine a văzut URL-ul poate trimite acolo ce dorește, iar sistemele dumneavoastră vor înregistra rezultatul ca pe un răspuns real la consultare. Pentru un consiliu local care numără obiecțiile la o cerere de autorizare, aceasta nu este o problemă teoretică.
Prin urmare, fiecare livrare este semnată. Fiecare webhook primește propriul secret generat aleatoriu, afișat în editor în spatele unui control de tip „arată secretul de semnare”. Noi calculăm un HMAC-SHA256 peste octeții exacți ai corpului cererii, folosind acel secret; dumneavoastră îl recalculați cu copia proprie și comparați. O cerere falsificată eșuează, pentru că falsificatorul nu are secretul.
URL-ul trebuie să fie https. Acest lucru este impus de o
constrângere din baza de date, nu de o simplă indicație în formular,
pentru că un secret de semnare care călătorește pe o conexiune necriptată
nu mai este un secret.
Verificarea unei livrări în Node.js
Două detalii produc, în practică, cele mai multe pagube. Folosiți corpul brut al cererii, nu un obiect analizat și reserializat — diferențele de spații albe schimbă hashul. Și comparați în timp constant, ca un atacator să nu poată afla semnătura corectă octet cu octet, din timpii de răspuns.
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);
}
Apelați funcția înainte de a analiza orice altceva. Dacă returnează false, răspundeți cu 401 și aruncați cererea. Dacă returnează true, aveți un răspuns care nu putea veni decât de la chestionarul dumneavoastră.
Jurnalul de livrări
Un webhook care a încetat în tăcere să funcționeze arată exact ca unul care nu a fost apelat niciodată: nu se întâmplă nimic și nimic nu vă anunță. De aceea, fiecare încercare este scrisă într-un jurnal de livrări, iar ultimul status HTTP este afișat în dreptul webhookului, în editor — verde pentru un 2xx, roșu pentru orice altceva.
Livrarea este asincronă, așa că statusul este reconciliat ulterior, nu presupus în momentul trimiterii. Ceea ce vedeți este statusul pe care l-a returnat efectiv serverul dumneavoastră. Fiecare webhook poate fi suspendat și reluat fără a fi șters, ceea ce este de obicei exact ce vă trebuie cât timp sistemul receptor este redistribuit.
O limită deliberată: nu există reîncercare cu întârziere exponențială. O livrare este încercată o dată, rezultatul este consemnat și cu asta basta. Construirea unei cozi de reîncercări care respectă ordinea și idempotența este muncă serioasă, iar a pretinde că avem una pe care nu am construit-o ar fi mai rău decât a spune acest lucru. Între timp, răspunsul nu se pierde niciodată — se află în baza de date, este vizibil în analiză și raportare și poate fi exportat în CSV.
Un webhook care eșuează nu poate nici să strice colectarea. Dacă livrarea aruncă o eroare, eroarea este consemnată, iar răspunsul este totuși înregistrat. Răspunsul respondentului este lucrul care contează; livrarea se face după cea mai bună intenție.
Notificări prin e-mail, fără răspunsuri
Adăugați adresele care ar trebui să afle despre răspunsurile noi și li se va trimite un e-mail la sosirea fiecăruia. Mesajul este HTML personalizat, cu o alternativă în text simplu, pentru că atât clienții de e-mail care afișează doar text, cât și filtrele antispam citesc partea simplă. E-mailul numește chestionarul, dă totalul curent al răspunsurilor și trimite către rezultate.
Ce nu conține sunt răspunsurile. Aceasta este o decizie, nu o omisiune. Un e-mail de notificare este redirecționat unui coleg, arhivat automat într-o căsuță comună, sincronizat pe un telefon și păstrat ani întregi într-o copie de siguranță. Introducerea în acest lanț a răspunsurilor de text liber dintr-o consultare împrăștie date cu caracter personal prin sisteme pe care nu le-a evaluat nimeni și anulează pe tăcute controalele de acces descrise în pagina de securitate. Datele respondenților rămân în baza de date, iar e-mailul vă spune doar să mergeți să vă uitați.
La fel ca webhookurile, notificările sunt declanșate de un declanșator din baza de date, deci urmează răspunsul, nu o anumită cale de trimitere. Dacă nu este configurată nicio adresă, nu se trimite nimic și nu se încearcă nimic.
Ce nu face
- Fără aplicație Zapier și fără conector Google Sheets.
- Fără reîncercare cu întârziere progresivă — o încercare pentru fiecare răspuns, apoi jurnalul.
- Fără alte evenimente de ieșire în afară de
response.created. Aprobările, modificările și ștergerile nu declanșează webhookuri. - Fără API de intrare pentru crearea de chestionare sau citirea programatică a răspunsurilor.
- Fără listă de adrese IP permise pentru livrări. Semnătura este autentificarea.
Întrebări frecvente
Cum verific antetul X-NumoForms-Signature?
Calculați un HMAC-SHA256 al corpului brut al cererii, folosind secretul de semnare al acelui webhook, codificați-l hexazecimal, adăugați prefixul „sha256=” și comparați-l cu antetul folosind o comparație în timp constant. Verificați înainte de a analiza JSON-ul și respingeți cu 401 orice nu se potrivește.
Ce se întâmplă dacă punctul meu final este indisponibil când sosește un răspuns?
Livrarea este încercată o singură dată, iar rezultatul este consemnat în jurnalul de livrări, împreună cu statusul HTTP. Nu există reîncercare automată cu întârziere progresivă. Răspunsul în sine nu este niciodată în pericol: eșecul unui webhook nu poate împiedica salvarea unui răspuns, iar datele pot fi oricând recuperate din ecranul de rezultate sau printr-un export CSV.
Se declanșează un webhook pentru un răspuns trimis dintr-un formular integrat?
Da. Livrarea este declanșată de un declanșator din baza de date, aplicat tabelului de răspunsuri, nu de o anumită pagină a aplicației, deci se produce indiferent de unde vine răspunsul — un link găzduit, un formular integrat sau un răspuns parțial reluat și tocmai finalizat.
Pot trimite un webhook către un URL http://?
Nu. O constrângere de verificare din baza de date impune ca URL-ul să înceapă cu https://, iar editorul refuză orice altceva. Un secret de semnare trimis pe o conexiune necriptată nu mai este un secret.
E-mailurile de notificare conțin răspunsurile respondentului?
Nu, în mod deliberat. E-mailul spune că a sosit un răspuns, numește chestionarul, dă totalul curent și trimite către rezultate. Notificările sunt redirecționate și rămân în copiile de siguranță ale căsuțelor poștale, așa că datele respondenților rămân în baza de date.
Există o aplicație Zapier sau o integrare cu Google Sheets?
Nu. Webhookurile sunt astăzi singura integrare de ieșire. Nu există o aplicație Zapier și nu există un conector Google Sheets, iar pentru niciunul dintre ele nu am anunțat o dată.
Conectați un chestionar la propriile sisteme.
Îndreptați un webhook către un punct final https și fiecare răspuns ajunge acolo semnat, astfel încât receptorul dumneavoastră să poată dovedi că a venit de la noi, nu de la cine a găsit URL-ul. Adăugați adresele care ar trebui anunțate prin e-mail la sosirea unui răspuns, iar răspunsurile rămân în baza de date, acolo unde sunt controalele de acces.
- Semnătură HMAC-SHA256 la fiecare livrare
- Jurnal de livrări care arată ultimul status HTTP
- Declanșat de un declanșator din baza de date, indiferent de calea de intrare