All guidesStripe and webhooks
On this page

Stripe webhooks

SupabaseStripe

Why does Stripe say "SubtleCryptoProvider cannot be used in a synchronous context" in a Supabase Edge Function?

By Ohad Krispin, founder of VibeRaven · LinkedIn

"SubtleCryptoProvider cannot be used in a synchronous context" means your Supabase Edge Function verifies a Stripe webhook with the synchronous constructEvent while stripe-node is using the Web Crypto provider, which only supports async operations. Replace it with await stripe.webhooks.constructEventAsync(body, signature, secret, undefined, Stripe.createSubtleCryptoProvider()), with body read by await req.text(). That is the setup Supabase's own example uses. Then turn off JWT verification for this one function (verify_jwt = false), because Stripe does not send a Supabase user token; the Stripe signature is what authenticates the request. Lovable apps on Supabase hit this when they add a Stripe webhook to an Edge Function.

npx viberaven check on a Vite + Supabase fixture whose stripe-webhook Edge Function reads req.json() and never verifies: unverified_stripe_webhook. After switching to constructEventAsync, the warning is gone.
npx viberaven check on a Vite + Supabase fixture whose stripe-webhook Edge Function reads req.json() and never verifies: unverified_stripe_webhook. After switching to constructEventAsync, the warning is gone.

Repository check

Check this in your repo

Find an Edge Function that trusts unverified events

For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reads supabase/functions as well as your app code, and reports unverified_stripe_webhook for a handler that never calls constructEvent or constructEventAsync, reads no stripe-signature header or reads no raw body. When Stripe code lives only in Edge Functions and none of them handles webhooks, it reports missing_stripe_webhook with a fix written for Deno.

It is advice, not a gate, and a repository check, not a live database or payment test: it does not read your Supabase secrets or your function's verify_jwt setting, and it does not call the deployed function.

npx -y viberaven@1.6.12 check

What it printed on a fixture repo

On 2026-10-23 we ran it on a small Vite + Supabase fixture repo made for this page, not a customer repo. Its supabase/functions/stripe-webhook/index.ts read the event with await req.json() and upserted a subscription row for checkout.session.completed without verifying it. The output is below, with the temp folder shortened to ~/stripe-app. A warning is not a blocker, so the exit code was 0.

After the function read await req.text() and verified with constructEventAsync, as in the example below, the warning was gone and only the missing_monitoring note stayed.

$ npx -y viberaven@1.6.12 check

viberaven check · ~/stripe-app

🟡 Stripe webhook does not verify the signature  (unverified_stripe_webhook)
   1 Stripe webhook handler without signature verification over the raw body: supabase/functions/stripe-webhook/index.ts (no signature check (constructEvent), no stripe-signature header, no raw body). Anyone who finds the URL can post a fake event, for example a paid checkout.
⚪ No error monitoring detected  (missing_monitoring)
   No Sentry/PostHog (or similar) instrumentation was found. Production errors will only surface when users complain.

Verdict: ✅ no blockers, 1 warning · score 85
Fix: viberaven fix · Details: ~/stripe-app/.viberaven/agent-tasklist.md

Supabase Edge Function Stripe webhook example

supabase/functions/stripe-webhook/index.ts, following Supabase's example. The function URL is https://<project-ref>.supabase.co/functions/v1/stripe-webhook; register that as the endpoint in Workbench.

import Stripe from 'npm:stripe@19';

const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!);
const cryptoProvider = Stripe.createSubtleCryptoProvider();

Deno.serve(async (req) => {
  const signature = req.headers.get('Stripe-Signature');
  const body = await req.text();
  let event: Stripe.Event;
  try {
    event = await stripe.webhooks.constructEventAsync(
      body, signature!, Deno.env.get('STRIPE_WEBHOOK_SECRET')!, undefined, cryptoProvider,
    );
  } catch (err) {
    return new Response((err as Error).message, { status: 400 });
  }
  // handle event.type
  return Response.json({ received: true });
});

Why constructEvent works in Next.js but throws in an Edge Function

In a Next.js route on Node.js, stripe-node signs with Node's own crypto module and the synchronous constructEvent works. Supabase's example passes Stripe.createSubtleCryptoProvider(), the Web Crypto API, whose computeHMACSignature throws CryptoProviderOnlySupportsAsyncError with this message when called synchronously. constructEventAsync takes the same arguments plus the provider and awaits the signature instead.

Stripe webhook returns 401 from a Supabase Edge Function

By default an Edge Function requires a valid JWT in the Authorization header, and Stripe has none. Supabase's docs use the Stripe webhook as their example of a function that must turn this off: set verify_jwt = false under [functions.stripe-webhook] in supabase/config.toml, or pass --no-verify-jwt when serving locally. Supabase warns that anyone can then call the function, which is why the signature check is not optional.

[functions.stripe-webhook]
verify_jwt = false

Stripe webhooks in a Lovable app

Lovable's Stripe integration runs payment code in Edge Functions and, by default, uses no webhooks. If you add one, the same rules apply: the function verifies with constructEventAsync, and the signing secret is stored as a secret, never in app code.

Checklist

Deploy and test the function

  1. 01Store the secrets on the project, not in code. Secret names cannot start with SUPABASE_, and deployed functions pick up new secrets without a redeploy.
  2. 02
    Set the Stripe key and the endpoint's signing secret:
    supabase secrets set STRIPE_SECRET_KEY=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_...
  3. 03Turn off JWT verification for this function only, in supabase/config.toml, then deploy it.
  4. 04Register https://<project-ref>.supabase.co/functions/v1/stripe-webhook in Workbench for the mode you use, and copy that endpoint's secret into STRIPE_WEBHOOK_SECRET.
  5. 05Write to your tables with the SUPABASE_SERVICE_ROLE_KEY (or secret key) Supabase provides to functions, and only after verification passes.
  6. 06Send a test event and check the function logs and the Event deliveries tab for a 200.

Related

Sources

Official docs behind this checklist

FAQ

FAQ for builders before launch

Can I keep JWT verification on and have Stripe send a token?

No. Stripe cannot send your users' tokens, so the function must accept requests without one and authenticate them by the Stripe signature instead.

Is turning off verify_jwt unsafe?

Only for a function that then trusts its input. With constructEventAsync over the raw body, a request without a valid Stripe signature gets a 400 and changes nothing.