85 85. Step 11: Deploy Supabase Edge Functions
As of Oct 22, 2026 · Official docs: Supabase Edge Functions · Supabase: Edge Function secrets · Stripe webhooks
Edge Functions are small back-end programs that run on Supabase’s servers. This app has three: one for AI labels, one for Stripe payments, and one for Stripe’s webhook.
Location in project: supabase/functions/, one folder per function. The folder’s name is the function’s name, and it is the name you use when you deploy it. The folder _shared is not a function: it holds code that the functions share, and the CLI includes it when it deploys a function that uses it.
85.1 The Three Functions
85.1.1 1. create-task-with-ai
Used to:
- Check which signed-in user is calling (from the user’s JWT, a signed “ID badge” that the app sends along).
- Save the new task in the
taskstable. - Ask OpenAI for a label (using the secret
OPENAI_API_KEY) and save the label with the task.
85.1.2 2. create-stripe-session
Used when the user clicks the subscription button in the app:
- For a free user, it creates a Stripe Checkout session (Stripe’s payment page) for the price in the secret
STRIPE_PRICE_ID. - For a premium user, it returns a link to Stripe’s customer portal, where the user can manage or cancel the subscription.
It uses the secrets STRIPE_SECRET_KEY and STRIPE_PRICE_ID.
85.1.3 3. stripe-webhook
Used to:
- Receive event notifications from Stripe.
- Verify each event’s signature using
STRIPE_WEBHOOK_SECRET. - Update the user’s row in the
profilestable:checkout.session.completedsetssubscription_plantopremium, andcustomer.subscription.deletedsets it back tofree. The code ignores every other event.
This function is called directly by Stripe, so it doesn’t use user JWTs.
85.2 The Keys and Secrets the Functions Read
The functions read their keys from environment variables on Supabase’s servers, never from files on your computer. There are two kinds:
| Name | What it holds | Who puts it there |
|---|---|---|
OPENAI_API_KEY |
Your OpenAI key | You, with npx supabase secrets set ((E) OpenAI API key in Classes 10–12) |
STRIPE_SECRET_KEY, STRIPE_PRICE_ID |
Your Stripe secret key and price ID | You, the same way (Step F) |
STRIPE_WEBHOOK_SECRET |
The webhook’s signing secret | You, at the end of this page |
SUPABASE_URL |
Your project’s address | Supabase, automatically |
SUPABASE_PUBLISHABLE_KEYS, SUPABASE_SECRET_KEYS |
Your project’s publishable (sb_publishable_...) and secret (sb_secret_...) keys |
Supabase, automatically |
The shared file supabase/functions/_shared/supabaseKeys.ts reads the last two. create-task-with-ai uses the publishable key together with the user’s login token, so the database’s security rules (RLS) still apply. create-stripe-session and stripe-webhook use the secret key, which skips those rules, so they can read and update any user’s profile. That is fine because the secret key never leaves Supabase’s servers.
A Supabase project can have several keys of each kind, so SUPABASE_PUBLISHABLE_KEYS and SUPABASE_SECRET_KEYS each hold a small JSON object of named keys, such as {"default": "sb_secret_..."}. A new project has one key of each kind, named default, and supabaseKeys.ts picks that one. If a variable has no key, the function stops with the error “No Supabase key found in …”, which points you to Project Settings > API Keys in the dashboard.
You may see older tutorials use the “anon” and “service_role” keys (the dashboard’s Legacy tab). This app’s code doesn’t read them any more: it uses only the new publishable and secret keys.
85.3 Deploy the Functions
Work in the project root, the task-app folder (it contains the supabase/ folder). Claude deploys the three functions by running these commands in the terminal for you, one at a time, after asking your permission. (To run one yourself, type it in VS Code’s terminal and press Enter.)
npx supabase functions deploy create-task-with-ainpx supabase functions deploy create-stripe-sessionnpx supabase functions deploy stripe-webhook --no-verify-jwtnpx supabaseruns the Supabase CLI from the project’s own packages (see Step 5a).functions deployuploads one function to Supabase’s servers, where it runs. The argument after it is the function’s name: the name of its folder insupabase/functions. The names use hyphens: the app calls the functions at URLs built from these exact names.--no-verify-jwt(only forstripe-webhook) turns off JWT verification for that function, so that Stripe can call it without a Supabase login. The section after next explains why.
Each function’s URL has this form:
https://[your-project-ref].supabase.co/functions/v1/create-task-with-ai
85.4 Verify Deployment
- In the Supabase dashboard, open Edge Functions.
- Confirm you see:
create-task-with-aicreate-stripe-sessionstripe-webhook
- Click a function to see its details. Its Logs tab shows runtime errors and the messages the code prints.
85.5 JWT verification for stripe-webhook
stripe-webhook must accept calls from Stripe, which don’t carry a Supabase JWT (a user’s login token). The --no-verify-jwt option above turns JWT verification off for that function only.
To check it, open Edge Functions > stripe-webhook > Details in the dashboard. The switch Verify JWT with legacy secret should be off. If it is on, turn it off and save, or deploy the function again with --no-verify-jwt.
The project’s supabase/config.toml file already contains these lines, so every deploy of stripe-webhook keeps verification off, even if someone forgets the option:
[functions.stripe-webhook]
verify_jwt = falseWhy is this safe? Stripe doesn’t send JWTs. Instead, it signs every event, and the function checks that signature with STRIPE_WEBHOOK_SECRET.
The other two functions keep JWT verification on. If creating a task or upgrading fails, open that function’s logs in the dashboard (Edge Functions) and show Claude what they say. For an “Invalid JWT” (401) error, one known fix, which Claude can make with you, is to turn off Verify JWT with legacy secret for that function too (Edge Functions > the function > Details). This is safe here: both functions check the user’s login themselves before they do anything.
85.6 Create the Stripe Webhook
A webhook is a web address that Stripe calls to tell your app that something happened, such as “this customer paid”. Now that stripe-webhook is deployed, give Stripe its address.
- Open dashboard.stripe.com/webhooks (in the same sandbox as the rest of your Stripe setup) and click Create an event destination:
Events from: Your account.
Payload style: Snapshot. Stripe suggests “Thin”, but this app’s code only understands Snapshot events.
Events:
checkout.session.completedandcustomer.subscription.deleted(the two the code handles; the README also listscustomer.subscription.updated, which does no harm).Destination type: Webhook endpoint.
Endpoint URL: your Stripe webhook URL from the worksheet:
https://[your-project-ref].supabase.co/functions/v1/stripe-webhook
- On the destination’s page, click Reveal next to the signing secret (
whsec_...) and copy it into the worksheet. - Copy the worksheet’s
STRIPE_WEBHOOK_SECRETline (annpx supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...command), paste it into VS Code’s terminal (in thetask-appfolder), and pressEnter. You type this one yourself, so the secret never goes into Claude’s chat.
If you already created an event destination with a temporary URL when you set up Stripe (Step 8), you can edit that one instead: change its Endpoint URL to the address above, and check its payload style and events. Its signing secret must be the one stored in STRIPE_WEBHOOK_SECRET.
You test the webhook in Step 12 by buying a subscription with a test card. Afterwards:
- The destination’s page in Stripe lists the events Stripe sent.
- The
stripe-webhooklogs in Supabase showReceived event: checkout.session.completed. - In the Supabase Table Editor, your row in
profileshassubscription_planset topremium.
Stripe can also send test events (with stripe trigger in the Stripe CLI), but those events create a new test customer who isn’t linked to any of your app’s users, so they don’t change your database.
Stripe may show an event as delivered even when the function rejected it: the function always answers “OK”. If an upgrade doesn’t appear in the app, read the stripe-webhook logs in Supabase.
Practice Questions
Easy
Question 1 · Short answer
What is a Supabase Edge Function, and in which folder of the project is the code for the three functions kept?
Question 2 · Matching
Match each function with its job. One job on the right does not belong to any of the three functions.
| Function | Job | |
|---|---|---|
| 1. The AI task-creation function | A. Receives event notifications from Stripe and updates the subscription status in the database | |
| 2. The Stripe session function | B. Asks OpenAI to suggest a label for a task and saves the task | |
3. stripe-webhook |
C. Stores the images that users upload | |
| D. Sends the user to a Stripe checkout page, or to Stripe’s customer portal if they already subscribe |
Question 3 · True or false
After you deploy a function, you can keep editing its file in supabase/functions/ on your laptop, and the live function on Supabase updates automatically.
Question 4 · Multiple choice
A function you deployed is returning errors. Where does this page tell you to look for the error messages?
- The function’s Logs tab under Edge Functions in the Supabase dashboard
- The
supabase/migrations/folder on your computer - The Google Cloud Console
- The
.env.localfile
Question 5 · Multiple choice
When you create the Stripe event destination, which payload style do you choose, and why?
- Thin, because Stripe suggests it
- Snapshot, because this app’s code only understands Snapshot events
- Either one: the code handles both
- Neither: payload style is only for live mode
Medium
Question 6 · Write the command
Write the command that deploys only the stripe-webhook function. In which folder should you be when you run it?
Question 7 · Write the URL
Your Supabase project ref is abcdefghijklmnopqrst. What is the URL of your deployed stripe-webhook function? (This is the URL you give to Stripe.)
Question 8 · Matching
Match each secret or credential with what it is used for.
| Secret or credential | Used for | |
|---|---|---|
1. OPENAI_API_KEY |
A. Proving which signed-in user is calling the AI task-creation function | |
2. STRIPE_SECRET_KEY |
B. Checking that an incoming webhook event really came from Stripe | |
3. STRIPE_WEBHOOK_SECRET |
C. Letting the AI task-creation function ask OpenAI for a label | |
| 4. The user’s JWT | D. Letting the Stripe session function create checkout sessions |
Question 9 · Multiple choice
A user who already has a subscription clicks “Manage Subscription”. According to the page, what does the Stripe session function do?
- Creates a second subscription for the same user
- Returns a link to Stripe’s customer portal, where the user can manage the existing subscription
- Deletes the user’s account
- Calls OpenAI to recommend a subscription plan
Question 10 · Matching
Match each environment variable that the deployed functions read with who puts it there.
| Variable | Who puts it there | |
|---|---|---|
1. OPENAI_API_KEY |
A. Supabase, automatically, for every function | |
2. SUPABASE_SECRET_KEYS |
B. You, with npx supabase secrets set, after creating the webhook in Stripe |
|
3. STRIPE_WEBHOOK_SECRET |
C. You, with npx supabase secrets set, after creating your OpenAI key |
Question 11 · True or false
If you deploy stripe-webhook and forget the --no-verify-jwt option, JWT verification is turned on for it, and Stripe’s calls are refused.
Hard
Question 12 · Long answer
Explain why JWT verification must be turned off for stripe-webhook, and why doing so does not let just anyone fake a payment.
Question 13 · Scenario
You deployed all three functions and pointed Stripe at the stripe-webhook URL. Now Stripe reports that every delivery to your endpoint fails with HTTP status 401 (Unauthorized), and the function’s logs show none of the messages your code would print. What did you most likely forget, and how do you fix it?
Question 14 · Scenario
When you set up Stripe, you created an event destination with a temporary URL (https://mybogusurl.com/). You deploy the functions but never change that URL. Then you buy a subscription with a test card. What happens, and how do you fix it?
Question 15 · Ordering
Put these steps in the order this page does them:
- A. Create the webhook in Stripe, with the deployed
stripe-webhookURL as its endpoint - B. Deploy the three functions with the Supabase CLI
- C. Check that JWT verification is off for
stripe-webhook - D. Check in the Supabase dashboard that all three functions appear, and look at their logs
Question 16 · Scenario
Sam creates a task, but no AI label appears. The create-task-with-ai logs in Supabase show an “Invalid JWT” (401) error, and none of the messages the function’s code prints. What is one known fix, and why is it safe for this function?
Question 17 · Long answer
create-task-with-ai uses the Supabase publishable key, while stripe-webhook uses the secret key. Explain why each function uses the key it does.