Base44 Stripe Webhook Not Working? Fix Signature Errors (2026)
Jump to: is it even reaching your function? · signature verification failed · runs but doesn't update · nothing arrives at all · two things worth clarifying
Start Here: Is Stripe Even Reaching Your Function?
- Check the delivery log first. In your Stripe Dashboard, open Workbench → Webhooks, click your endpoint, and look at its recent delivery attempts. This tells you immediately whether Stripe is calling your endpoint at all, and what status code it got back.
- Check your function's own logs. In Base44, go to Dashboard → Code → Functions and look for a red error message on the request in question.
- Check the mode. An endpoint registered while looking at Stripe's test data only receives test-mode events, and one registered under your live account only receives live events; the two don't share deliveries.
- Check the URL. Confirm the endpoint you registered in Stripe matches the one Base44 actually serves for that function:
https://<your-app-domain>/functions/<function-name>.
If the delivery log shows a 200 and your function's logs show it ran, skip to the function runs but your app doesn't update. If the log shows a failed delivery with a signature error, go to Error 1. If nothing shows up in the log at all, go to Error 3.
Error 1 - Webhook Signature Verification Failed
What you see: Stripe's delivery log shows a failed attempt, and your function's error is something like Webhook signature verification failed. Err: No signatures found matching the expected signature for payload.
Why it happens
Base44's own documentation for backend functions shows await req.json() as the example for reading a function's request body. Stripe signs the exact raw bytes it sends, and its own troubleshooting guide names a parsed or mutated body as one of the most common causes of this exact error: once the body has already been parsed into JSON, had whitespace changed, or had its keys reordered, the string your code verifies no longer matches what Stripe signed. Two other causes from the same Stripe guide are verifying against the wrong secret (a Dashboard-registered endpoint and a stripe listen-forwarded session each print their own, different whsec_ secret, and one never verifies events signed with the other's) and a malformed read of the Stripe-Signature header, which should look like t=xxx,v1=yyy,v0=zzz.
How to fix it
- Read the incoming request as raw text, not parsed JSON:
const rawBody = await req.text();. A Base44 function receives a standardRequestobject, sotext()is available the same wayjson()is in Base44's own example; the difference is thattext()leaves the bytes untouched. - Verify that raw string with Stripe's SDK before doing anything else with it, using the async version:
await stripe.webhooks.constructEventAsync(rawBody, signature, endpointSecret), wheresignatureis theStripe-Signatureheader from the request. Base44's own documentation confirms backend functions run on Deno, and Stripe shipsconstructEventAsyncspecifically for runtimes like it where the synchronousconstructEventisn't guaranteed to work; use the async version by default here rather than checking case by case. Only parse the verified body into JSON afterward. - Confirm which secret you're loading. In Workbench → Webhooks, open your endpoint and click "Reveal secret." That value only verifies events sent to that same registered endpoint. A secret printed by
stripe listenonly verifies events forwarded by that CLI session. Print the secret your function loads at request time and compare it, character for character, with the one Stripe shows. - Store the secret as a Base44 secret (for example
STRIPE_WEBHOOK_SECRET), read inside the handler withsecrets.get()(not at the top of the file, since secrets resolve per request, not at module load), and never as aVITE_variable, which ships to the browser.
What you should see: Workbench's delivery log shows a 200 for the attempt, and your function's own logs show the event type it parsed, for example payment_intent.succeeded, instead of a signature error.
Error 2 - The Function Runs, but Your App Doesn't Update
What you see: the delivery log shows a successful 200, your function's logs show it ran, but the matching order, subscription or record in your app never changes status.
Why it happens
Base44's functions documentation is explicit that a function called via direct HTTP (which is exactly what a webhook is) has no authenticated user context. If the handler reads or writes app data the same way it would for a logged-in user's request, that operation can silently fail or apply to the wrong scope, because there's no logged-in user behind a Stripe callback.
How to fix it
- Use
asServiceRolefor every read or write the handler makes to app data; this is what Base44's own documentation directs for functions invoked via direct HTTP. - Match the incoming event to your own record with an identifier you set yourself when creating the Stripe object (a
client_reference_idor ametadatafield on the PaymentIntent or Checkout Session), not the logged-in user's session; there isn't one at webhook time. - Return the 2xx response before the slower part of the update finishes, as Stripe's own guidance recommends, so a slow database write doesn't turn into a delivery timeout that Stripe then retries.
What you should see: after a test event, the matching record in your app updates status, and the function's logs show the asServiceRole write succeeding instead of a silent no-op.
Error 3 - No Events Are Showing Up in Stripe at All
What you see: Workbench shows no delivery attempts for the endpoint, not even failed ones.
Why it happens
This points to something before your code ever runs: the endpoint was never registered, it was registered under the wrong Stripe mode, or the URL registered doesn't match what Base44 actually serves.
How to fix it
- In Workbench → Webhooks, confirm an endpoint exists with the exact URL your function is served at:
https://<your-app-domain>/functions/<function-name>. - Confirm you're looking at the matching mode. Base44's own Stripe setup runs in a sandbox by default; if the webhook was registered against your live Stripe account before you claimed that sandbox and went live (Dashboard → Integrations → Stripe → "Claim & Go Live"), test-mode events won't reach it, and the reverse is also true.
- Confirm which events are enabled on that endpoint; one with no events selected, or the wrong ones, won't deliver the event your code is waiting for.
- If you're testing before you have a public URL, run
stripe listen --forward-to <your endpoint URL>to forward events to it locally. To fire a specific event on demand, usestripe trigger <event-name>as its own separate command.
What you should see: a new entry appears in Workbench's delivery log within seconds of triggering the event, showing a response code your function actually returned.
Two Things Worth Clarifying
Most generic Stripe-webhook material assumes neither of these is true for Base44:
- Base44 ships its own backend: database, authentication, file storage, functions and hosting, with row-level security built in. Supabase is not part of that stack. Guides written for a Supabase-backed app (Edge Functions, a
service_rolekey in an.envfile) assume a different backend and don't apply here, unless you've separately connected an external Supabase project yourself. - Base44's own built-in Stripe integration doesn't hand you a webhook. Base44's setup documentation for payments doesn't cover webhook configuration and instead recommends confirming a payment and updating the customer's account while they're still logged in "instead of relying on webhooks alone." If a webhook exists in your app, it was added through a backend function on top of that built-in flow. That's a reasonable thing to do for anything that has to update the app when the customer isn't actively looking, like a delayed bank payment or a subscription renewal, but it's work that was added, not something Base44 wires up by default.
When It Isn't a Bug
- A timeout under load isn't necessarily a platform bug. Stripe expects a fast 2xx response before any slow logic runs; a handler that does everything before responding will look like it's failing when it's really just answering too late.
- Losing test events after switching Stripe modes isn't a bug. An endpoint only receives events from the mode it was registered under; moving your Base44 app from sandbox to a live Stripe account doesn't move an existing endpoint's registration with it.
- The 5-minute function timeout applies to webhook handlers too, the same as any other Base44 function call. If event handling regularly runs anywhere near that, that's a scope problem to fix in the handler, not something to work around.
What to Include When You Ask for Help
- the exact error text and response code from Stripe's delivery log (Workbench → Webhooks → your endpoint);
- whether it happened in Stripe's test mode or live mode;
- your function's own logs for that request (Dashboard → Code → Functions in Base44);
- the event type you expected (for example
payment_intent.succeeded) and whether it's enabled on that endpoint.
Related
If backend functions are failing outside of Stripe too (404s, 500s or ISOLATE_INTERNAL_FAILURE), see the full Base44 not working guide for that broader case. For an overview of what Base44's platform covers, see the Base44 platform page.
Frequently Asked Questions
What is a Stripe webhook, and does my Base44 app need one?
A Stripe webhook is an HTTPS endpoint you register so Stripe can push event data to your app in real time, for things like a bank confirming a payment or a customer disputing a charge. Base44's own built-in Stripe integration doesn't require one; it leans on confirming payment while the customer is still logged in, so you only need a webhook if it was added through a backend function for something that has to update your app when nobody's actively looking, like a delayed payment or a subscription renewal.
Why does my Base44 Stripe webhook fail with "No signatures found matching the expected signature for payload"?
Almost always because the body being verified isn't the exact raw string Stripe sent. Base44's own example for reading a function's request body is await req.json(), which parses the body before you can verify it. Switch to await req.text() and verify that raw string first with Stripe's SDK, only parsing it into JSON after the signature check passes.
Where do I find my webhook signing secret for a Base44 app?
In your Stripe Dashboard, under Workbench, open Webhooks, select the endpoint, and click "Reveal secret." It starts with whsec_. Store it as a Base44 secret, such as STRIPE_WEBHOOK_SECRET, and read it inside your function handler with secrets.get(). Never store it as a VITE_ variable, which would ship it to the browser.
Does Base44's built-in Stripe integration set up a webhook for me?
No. Base44's own setup documentation for payments doesn't cover webhook configuration, and instead recommends confirming payment status while the customer is still logged in rather than relying on webhooks alone. A webhook in a Base44 app is something added through a backend function on top of that built-in flow.
Why don't any events show up in Stripe's webhook log for my Base44 endpoint at all?
Usually a registration mismatch rather than a code bug: the endpoint URL registered in Workbench doesn't match the one your function is actually served at, or the endpoint was registered under the wrong Stripe mode for the events being generated. Check both of those before assuming the function itself is broken.
Sources
Checked on September 24, 2026: