Supabase
Supabase is a good fit when your app already uses Supabase Auth or Postgres and you want billing checks to live near the rest of your backend code.
The @guapocado/supabase package is intentionally small. It exports a standard
Deno HTTP handler for Supabase Edge Functions. It does not pull in Supabase
client libraries, own your auth model, or register webhooks unless you enable
that route.
Supabase documents Edge Functions as Deno-compatible TypeScript functions, and
their routing guide shows
handlers served with Deno.serve(handler) and paths prefixed with the function
name. Guapocado follows that model.
Install
Section titled “Install”In a Supabase Edge Function, import the package through Deno’s npm support:
import { handler } from "npm:@guapocado/supabase";
Deno.serve(handler);Set your Guapocado server key as a Supabase secret:
supabase secrets set GUAPOCADO_API_KEY=guap_sk_...Create a function named guap:
supabase functions new guapThen put this in supabase/functions/guap/index.ts:
import { handler } from "npm:@guapocado/supabase";
Deno.serve(handler);Deploy it:
supabase functions deploy guap --no-verify-jwtUse --no-verify-jwt when your function code is responsible for auth. Supabase
can otherwise reject requests before your handler runs; their
401 troubleshooting guide
now recommends handling auth in function code when you need that control.
Routes
Section titled “Routes”With a function named guap, call routes under:
https://<project-ref>.supabase.co/functions/v1/guapAvailable routes:
| Route | Purpose |
|---|---|
GET /health | Function health check |
GET /features/:key?customerId=... | Check a boolean feature |
GET /limits/:key?customerId=... | Read an effective numeric limit |
GET /usage/:key?customerId=... | Read meter balance |
POST /usage/:key/consume | Consume meter usage |
POST /usage/:key/refund | Refund meter usage |
POST /context | Fetch features, usage, limits, plans, and subscription in one request |
POST /checkout | Create a checkout session |
GET /plans | List configured products |
GET /subscription?customerId=... | Read the current subscription |
POST /subscription/change | Change the subscription product |
POST /customers | Create or sync a customer |
The default handler accepts customerId from query strings or JSON bodies. That
is useful for server-to-server calls and early internal testing. Do not use that
default for browser-callable functions unless your app has another access
control layer.
Check a Feature
Section titled “Check a Feature”curl \ "https://<project-ref>.supabase.co/functions/v1/guap/features/exports?customerId=user_123"Response:
{ "key": "exports", "hasAccess": true}Fetch Context
Section titled “Fetch Context”Use context when your app needs several checks for the same customer.
curl \ "https://<project-ref>.supabase.co/functions/v1/guap/context" \ -H "content-type: application/json" \ -d '{ "customerId": "user_123", "features": ["exports"], "usage": ["ai_credits"], "limits": ["projects"] }'Consume Usage
Section titled “Consume Usage”curl \ "https://<project-ref>.supabase.co/functions/v1/guap/usage/ai_credits/consume" \ -H "content-type: application/json" \ -d '{ "customerId": "user_123", "amount": 1 }'Consume usage after your app has decided that the action should count. If the expensive work can fail after consumption, refund the amount on failure.
Secure Customer Resolution
Section titled “Secure Customer Resolution”For browser-callable functions, resolve the Guapocado customerId from
Supabase Auth or from the user’s active organization. The handler exposes a
customerId hook for that.
This example scopes billing to the authenticated Supabase user ID:
import { createGuapocadoSupabaseHandler } from "npm:@guapocado/supabase";import { createClient } from "npm:@supabase/supabase-js@2";
const handler = createGuapocadoSupabaseHandler({ allowRequestCustomerId: false, customerId: async (request) => { const authorization = request.headers.get("Authorization"); if (!authorization) return undefined;
const supabase = createClient( Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_ANON_KEY") ?? "", { global: { headers: { Authorization: authorization }, }, }, );
const { data, error } = await supabase.auth.getUser(); if (error || !data.user) return undefined;
return data.user.id; },});
Deno.serve(handler);If your app bills organizations or workspaces, return the organization ID instead. That resolver is also the right place to query Supabase Postgres and confirm that the authenticated user belongs to the organization they are acting inside.
Browser Calls
Section titled “Browser Calls”Call the Supabase function with the user’s Supabase access token. The exact
client wrapper is up to your app; the important part is that the
Authorization header reaches the Edge Function.
const response = await fetch( `${SUPABASE_URL}/functions/v1/guap/context`, { method: "POST", headers: { "content-type": "application/json", Authorization: `Bearer ${session.access_token}`, }, body: JSON.stringify({ features: ["exports"], usage: ["ai_credits"], limits: ["projects"], }), },);
const context = await response.json();Do not send customerId from the browser in this pattern. The function resolves
it from the Supabase token.
Checkout
Section titled “Checkout”const response = await fetch( `${SUPABASE_URL}/functions/v1/guap/checkout`, { method: "POST", headers: { "content-type": "application/json", Authorization: `Bearer ${session.access_token}`, }, body: JSON.stringify({ productKey: "pro", successUrl: `${APP_URL}/billing/success`, cancelUrl: `${APP_URL}/billing`, }), },);
const { url } = await response.json();window.location.href = url;The handler uses the resolved customerId when creating checkout sessions. If
your customer record needs an email or Stripe customer ID, create or sync the
customer first with the server SDK or POST /customers.
Webhooks
Section titled “Webhooks”Webhook registration is off by default. Enable it only when you want the Supabase function to register Guapocado webhook endpoints.
import { createGuapocadoSupabaseHandler } from "npm:@guapocado/supabase";
const handler = createGuapocadoSupabaseHandler({ webhooks: { enabled: true, registrationKey: Deno.env.get("GUAPOCADO_WEBHOOK_REGISTRATION_KEY"), },});
Deno.serve(handler);This does not install a local webhook projection table or sync process. If you want a Supabase Postgres read model, generate Postgres tables with the CLI and wire the server SDK adapter in your own backend code.
Exported Handler Pattern
Section titled “Exported Handler Pattern”Some Deno adapters and file-based routers ask for an ALL export. Supabase Edge
Functions should still use Deno.serve(handler), but the package includes an
alias for adapters with that convention:
import { handler } from "@guapocado/supabase";
export const ALL = handler;You can also import the alias directly:
export { ALL } from "@guapocado/supabase";Configuration
Section titled “Configuration”Most apps only need this:
createGuapocadoSupabaseHandler({ customerId: async (request) => { // Resolve from Supabase Auth, a workspace membership row, or another // trusted server-side source. },});Supported options:
| Option | Purpose |
|---|---|
apiKey | Override the default GUAPOCADO_API_KEY secret lookup |
customerId | Resolve the customer for this request |
allowRequestCustomerId | Allow customerId from query/body when no resolver returned one |
cors | Disable or customize CORS headers |
routePrefix | Strip a custom path prefix before route matching |
webhooks | Enable the webhook registration route |
onError | Log unexpected handler errors |
Keep the Guapocado server key in Supabase secrets. Never send it to the browser.