API di FlavosoWebhook ed eventiVerificare le firme
Verificare le firme
Ogni consegna contiene Flavoso-Signature, un HMAC-SHA256 su timestamp e corpo grezzo. Codice di verifica per Node.js, Python, PHP, Go e C#.
Lo schema
Ogni consegna contiene l'intestazione Flavoso-Signature: t=1759579200,v1=5257a8…. Qui t è il tempo in secondi dal 1970 e v1 un HMAC-SHA256, in esadecimale minuscolo:
- Chiave: il tuo segreto di firma, l'intera stringa, compreso
whsec_, in UTF-8. - Messaggio:
t, un punto, il corpo grezzo. Quindi1759579200.{"id":"evt_…",…}.
Verifica la firma prima di fidarti del contenuto. Così sai che la consegna viene da Flavoso e non è stata alterata per strada. È lo stesso schema di Stripe: se l'hai implementato lì, lo conosci già.
Passo per passo
- 1
Prendi il corpo grezzo
Esattamente i byte arrivati, prima che qualcosa li legga come JSON.
- 2
Scomponi l'intestazione
Dividi alle virgole, poi al primo
=.tc'è una volta,v1una o due volte. - 3
Controlla l'età
Se
tsi discosta dal tuo orologio di più di 300 secondi, rifiuta. Altrimenti una consegna intercettata si potrebbe riprodurre più tardi. - 4
Calcola il valore atteso
HMAC-SHA256 con il segreto su
t+.+ corpo, in esadecimale. - 5
Confronta
Confronta con ogni
v1in tempo costante. Se uno corrisponde, la consegna è autentica.
Codice di verifica
// flavoso-signature.js
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the body exactly as received. Never JSON.stringify(req.body) again.
export function verifyFlavosoSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = header.split(",").map((part) => part.trim().split("="));
const t = Number(parts.find(([key]) => key === "t")?.[1]);
const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value);
if (!Number.isInteger(t) || signatures.length === 0) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`, "utf8").digest("hex");
return signatures.some(
(s) => s.length === expected.length && timingSafeEqual(Buffer.from(s), Buffer.from(expected)),
);
}Ottenere il corpo grezzo
La maggior parte dei framework legge il JSON da sola. Se poi riconverti il corpo in testo, ottieni caratteri diversi (spazi, ordine, caratteri accentati) e una firma sbagliata. Ecco come ottenere i byte:
| Ambiente | Corpo grezzo |
|---|---|
| Express | express.raw({ type: "application/json" }) su questo endpoint |
| Next.js (App Router) | await request.text() |
| Flask | request.get_data() |
| FastAPI | await request.body() |
| Django | request.body |
| PHP | file_get_contents('php://input') |
| Laravel | $request->getContent() |
| Go | io.ReadAll(r.Body) |
| ASP.NET Core | StreamReader su Request.Body, prima di qualsiasi model binding |
// app/api/flavoso/webhook/route.js (App Router)
import { verifyFlavosoSignature } from "@/lib/flavoso-signature";
export async function POST(request) {
const raw = await request.text(); // exactly as received, before any JSON parsing
const header = request.headers.get("flavoso-signature") ?? "";
if (!verifyFlavosoSignature(raw, header, process.env.FLAVOSO_WEBHOOK_SECRET)) {
return new Response("invalid signature", { status: 400 });
}
await enqueue(JSON.parse(raw)); // answer fast, work in the background
return new Response(null, { status: 200 });
}Valore di prova
Con questo esempio puoi testare la tua verifica senza Flavoso: con questo segreto, questo corpo e t impostato ad «adesso» deve accettare la firma, e rifiutarla se nel corpo cambia anche un solo carattere.
# Your verification must accept this example with t = 1759579200 as "now".
# openssl prints the same v1 (after "SHA2-256(stdin)="):
printf '%s' '1759579200.{"id":"evt_test","type":"ping"}' \
| openssl dgst -sha256 -hmac 'whsec_test_secret'
# secret: whsec_test_secret
# body: {"id":"evt_test","type":"ping"}
# header: Flavoso-Signature: t=1759579200,v1=d5a5a8b08d9a25070a9e22c9fa06ad59b4082eb039b25cee3d83319e00d8201aCambiare il segreto
Dopo «Crea un nuovo segreto di firma» Flavoso firma per 24 ore con il segreto nuovo e con quello vecchio insieme, prima il nuovo: l'intestazione contiene allora due v1. Basta che uno corrisponda. In questo periodo imposta il nuovo segreto; dopo vale solo quello nuovo.
Errori frequenti
- Il corpo è stato letto come JSON e riconvertito in testo.
- Il segreto sbagliato: il webhook della tua app e i webhook propri di un locale hanno segreti diversi.
- L'orologio del server è sbagliato. Sincronizzalo con NTP.
- Esadecimale confrontato in maiuscolo, oppure spazi intorno alle parti dell'intestazione non rimossi.
Flavoso-Event-Idpreso come prova. L'identificativo non è un segreto; solo la firma prova qualcosa.
Qualcosa non è chiaro? Scrivi a support@flavoso.com indicando l'identificativo di X-Request-Id. Così troviamo la tua richiesta nel registro.