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. Quindi 1759579200.{"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. 1

    Prendi il corpo grezzo

    Esattamente i byte arrivati, prima che qualcosa li legga come JSON.

  2. 2

    Scomponi l'intestazione

    Dividi alle virgole, poi al primo =. t c'è una volta, v1 una o due volte.

  3. 3

    Controlla l'età

    Se t si discosta dal tuo orologio di più di 300 secondi, rifiuta. Altrimenti una consegna intercettata si potrebbe riprodurre più tardi.

  4. 4

    Calcola il valore atteso

    HMAC-SHA256 con il segreto su t + . + corpo, in esadecimale.

  5. 5

    Confronta

    Confronta con ogni v1 in 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:

AmbienteCorpo grezzo
Expressexpress.raw({ type: "application/json" }) su questo endpoint
Next.js (App Router)await request.text()
Flaskrequest.get_data()
FastAPIawait request.body()
Djangorequest.body
PHPfile_get_contents('php://input')
Laravel$request->getContent()
Goio.ReadAll(r.Body)
ASP.NET CoreStreamReader 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=d5a5a8b08d9a25070a9e22c9fa06ad59b4082eb039b25cee3d83319e00d8201a

Cambiare 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.

Se un segreto è diventato pubblico, rinnovalo subito. Le consegne firmate con quello vecchio restano possibili per altre 24 ore, non di più.

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-Id preso 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.