Validatore di firma webhook
Il tuo provider invia un header di firma, il tuo handler dice che non corrisponde, e la documentazione risponde con un SDK da installare. Non ti dice perché. Incolla firma, secret e body grezzo qui sotto: ricalcoliamo l'HMAC nel tuo browser e, se fallisce, riproviamo le mutazioni che di solito lo causano — uno spazio di troppo nel secret, un a capo finale, un body ri-serializzato, un timestamp scaduto.
In breve: una firma webhook fallisce quasi sempre per una di cinque ragioni — secret sbagliato (test vs live), un body ri-serializzato dal framework, un timestamp fuori dalla finestra di 5 minuti, un a capo finale o un prefisso sha256= / v0= rimasto. Questo strumento ricalcola l'HMAC e ti dice quale.
Incolla una firma che fallisce
I quattro schemi di firma
Tutti e quattro sono HMAC, ma differiscono su cosa viene hashato e come si codifica il risultato. Un dettaglio sbagliato e ogni consegna fallisce.
| Provider | Cosa viene firmato | Algoritmo |
|---|---|---|
| Stripe | HMAC di t.body; firma e timestamp in un unico header Stripe-Signature | SHA-256 hex |
| GitHub | HMAC del body grezzo; firma in X-Hub-Signature-256 | SHA-256 hex |
| Shopify | HMAC del body grezzo; firma in X-Shopify-Hmac-Sha256 | SHA-256 base64 |
| Slack | HMAC di v0:timestamp:body; serve anche l'header timestamp | SHA-256 hex |
Twilio è l'eccezione — hasha una URL canonica più parametri ordinati, non il body. Usa il validatore di firma Twilio per quello.
Le cinque cause reali
Ordinate per quanto spesso compaiono nei thread di supporto.
| # | Causa | Sintomo | Soluzione |
|---|---|---|---|
| 1 | Secret sbagliato | Ogni consegna fallisce, anche una nuova | Confusione test vs live — ogni provider emette un secret di firma diverso per modalità e per endpoint. |
| 2 | Body ri-serializzato | Fallisce dopo che il framework ha parsato il JSON | Firma i byte grezzi, non JSON.stringify(parsed): spaziatura e ordine delle chiavi cambiano l'hash. |
| 3 | Timestamp scaduto | Passa nei test, fallisce su eventi rinviati | Stripe e Slack rifiutano oltre 5 minuti. Non rinviare un evento catturato ore dopo. |
| 4 | A capo finale | Body sfasato di un byte, firma mai valida | Un proxy o editor ha aggiunto un a capo. Lo strumento segnala questo caso esatto. |
| 5 | Prefisso confrontato alla lettera | sha256= o v0= incluso nel confronto | Confronta solo la parte hex/base64, non il prefisso dello schema. |
La trappola del body grezzo
La causa numero uno: verifichi contro il body parsato-poi-ri-serializzato invece dei byte esatti. JSON.stringify(JSON.parse(body)) cambia spaziatura e ordine delle chiavi, quindi l'HMAC non corrisponde più. Leggi il body grezzo prima che qualcosa lo parsi.
// Express: keep the RAW bytes, do NOT let JSON re-serialise them
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
const raw = req.body; // Buffer, exactly as received
verify(raw, req.headers['stripe-signature'], secret);
});# Flask: request.get_data() returns the raw body; request.json does NOT raw = request.get_data() # bytes, untouched verify(raw, request.headers['X-Hub-Signature-256'], secret)
// Next.js App Router: read the text before parsing
export async function POST(req) {
const raw = await req.text(); // sign THIS, not JSON.stringify(await req.json())
verify(raw, req.headers.get('stripe-signature'), secret);
}Regola pratica: cattura firma e byte grezzi al bordo, verifica, poi parsa.
Timestamp e replay
Stripe e Slack integrano un timestamp nella stringa firmata e rifiutano tutto ciò che supera cinque minuti — è protezione anti-replay, non un bug. GitHub e Shopify no, quindi una firma GitHub valida resta valida per sempre. Se lo strumento dice valida ma scaduta, secret e body sono a posto; stai solo rinviando un vecchio evento. Genera una consegna nuova invece di rinviarne una catturata.
Quando la firma è corretta
Devi inviare un evento di test firmato correttamente al tuo endpoint? Il signer di webhook ne costruisce uno per tutti e sei i provider. Per catturare e ispezionare consegne reali, puntale a un tunnel Relay.
Domande frequenti
Salvate il mio secret di firma?
No. L'HMAC è calcolato nel tuo browser con Web Crypto; secret e body non raggiungono mai i nostri server. Apri il tab Rete: cliccando Verifica non parte alcuna richiesta.
Quali provider sono supportati?
Stripe, GitHub, Shopify e Slack — i quattro schemi HMAC sul body. Twilio firma invece una URL canonica, quindi ha uno strumento dedicato: il validatore di firma Twilio.
Passa qui ma il mio server la rifiuta comunque. Perché?
Quasi sempre il body. Il framework ha parsato il JSON e tu l'hai ri-serializzato prima di verificare, il che cambia spaziatura e ordine delle chiavi. Firma i byte grezzi — vedi gli snippet Express, Flask e Next sopra.
Cosa significa «valida ma scaduta»?
L'HMAC è corretto, ma Stripe e Slack rifiutano anche le firme di oltre cinque minuti per bloccare i replay. Se hai catturato un evento e lo rinvii dopo, la verifica fallisce sul timestamp anche se il secret è giusto.