Ak dostávate 400 Bad Request s hláškou “Invalid signature” zo Stripe
webhooku na Cloudflare Workers alebo Cloudflare Pages, skutočný problém
nemusí byť váš tajný kľúč webhooku.
Príznak #
Endpoint Stripe webhooku vracia:
400 Bad Request
Invalid signature
V Stripe Dashboard pod Developers → Webhooks doručenie zobrazuje:
400 ERR
Bad Request
Response body: Invalid signature
STRIPE_WEBHOOK_SECRET som skontroloval trikrát. Zregeneroval som ho.
Znova som nasadil. Stále zlyháva.
Zavádzajúca chyba #
Štandardný kód na overenie Stripe webhooku vyzerá takto:
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
} catch (err) {
console.error("Webhook signature verification failed:", err)
return new Response("Invalid signature", { status: 400 })
}
Problém je v tom, že chyba je zachytená a vrátené je “Invalid signature” bez toho, aby sme sa pozreli na to, aká je skutočná chyba.
Skutočná chyba #
Keď som pridal správne logovanie a skontroloval real-time logy Cloudflare:
wrangler pages deployment tail --project=your-project
Videl som toto:
(error) Webhook signature verification failed: Error: SubtleCryptoProvider cannot be used in a synchronous context.
Use `await constructEventAsync(...)` instead of `constructEvent(...)`
Podpis bol v poriadku. Problémom bol crypto provider.
Prečo sa to deje #
Cloudflare Workers používa Web Crypto API (SubtleCrypto), ktoré je len
asynchrónne. Metóda constructEvent() Stripe SDK sa pokúša použiť crypto
synchrónne, čo v prostredí Workers zlyháva.
Stripe SDK vám vlastne v chybovej správe presne hovorí, čo robiť, ale ak chybu zachytávate a ignorujete (ako väčšina ukážkových kódov), nikdy ju neuvidíte.
Oprava #
Zmeňte zo synchrónnej verzie:
event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
Na asynchrónnu:
event = await stripe.webhooks.constructEventAsync(
body,
signature,
webhookSecret
)
To je všetko. Zmena jedného slova: constructEvent → constructEventAsync
(a pridanie await).
Kompletný funkčný príklad #
import type { APIRoute } from "astro"
import Stripe from "stripe"
export const POST: APIRoute = async ({ request, locals }) => {
const stripe = new Stripe(locals.runtime.env.STRIPE_SECRET_KEY)
const webhookSecret = locals.runtime.env.STRIPE_WEBHOOK_SECRET
const body = await request.text()
const signature = request.headers.get("stripe-signature")
if (!signature) {
return new Response("Missing signature", { status: 400 })
}
let event
try {
// Use constructEventAsync for Cloudflare Workers/Pages
event = await stripe.webhooks.constructEventAsync(
body,
signature,
webhookSecret
)
} catch (err) {
console.error("Webhook verification failed:", err)
return new Response("Invalid signature", { status: 400 })
}
// Handle the event
if (event.type === "checkout.session.completed") {
const session = event.data.object
// Process the checkout...
}
return new Response("OK", { status: 200 })
}
Tipy na ladenie #
Ak stále máte problémy, pridajte dočasné logovanie, aby ste videli, čo sa skutočne deje:
console.log("Webhook debug:", {
hasSecret: !!webhookSecret,
secretPrefix: webhookSecret?.substring(0, 10),
hasSignature: !!signature,
bodyLength: body.length,
})
Potom skontrolujte logy pomocou:
wrangler pages deployment tail --project=your-project
Zhrnutie #
- Chyba:
Invalid signature/400 Bad Request - Skutočná príčina:
SubtleCryptoProvider cannot be used in a synchronous context - Oprava: Použiť
constructEventAsync()namiestoconstructEvent() - Platí pre: Cloudflare Workers, Cloudflare Pages a akýkoľvek edge runtime používajúci Web Crypto API
Pri tomto riešení som sa naučil, ako ladiť takéto situácie. Je to podobné
ako console.log v prehliadači. Nie je to absolútne najlepší spôsob, ale
prácu odvede. Enjoy!