Verifying Stripe webhooks in Lovable, Bolt and Supabase Edge Functions

Updated

How do I verify Stripe webhooks in a Supabase Edge Function?

Read the raw request body and the stripe-signature header and verify them with your webhook signing secret before trusting the event. In Deno (Supabase Edge Functions) use the async verifier with the SubtleCrypto provider. Grant paid access only from the verified webhook, never from the success page.

Supabase Edge Function (Deno)

import Stripe from "npm:stripe";
const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!);
const crypto = Stripe.createSubtleCryptoProvider();

Deno.serve(async (req) => {
  const body = await req.text();
  const signature = req.headers.get("stripe-signature")!;
  let event: Stripe.Event;
  try {
    event = await stripe.webhooks.constructEventAsync(body, signature, Deno.env.get("STRIPE_WEBHOOK_SECRET")!, undefined, crypto);
  } catch {
    return new Response("Invalid signature", { status: 400 });
  }
  // handle event.type here
  return new Response("ok");
});

Next.js route handler

export async function POST(req: Request) {
  const event = stripe.webhooks.constructEvent(await req.text(), req.headers.get("stripe-signature")!, process.env.STRIPE_WEBHOOK_SECRET!);
  // ...
}

Mistakes we see in AI-built apps

  • Parsing the body with req.json() and trusting event.type without verification.
  • Marking the user as paid on the success redirect page.
  • Taking the price or amount from the request when creating the Checkout Session.
  • Updating "premium" or "credits" columns from browser code.

Questions

Should verify_jwt be off for the webhook function?

Yes, Stripe cannot send a Supabase JWT. The signature check is what protects it.

Where do I find the webhook secret?

In the Stripe dashboard under the webhook endpoint (it starts with whsec_). Store it as an environment variable.