Introduction
You set up API key authentication for your Next.js routes. Zuplo is protecting your endpoints. Everything works—for you.
Now a real user signs up and lands in your developer portal but doesn't have any API keys to access your route. They can’t even create one themselves unless you do it for them. More than half of users won’t even bother to message you; they’ll drop off.
The fix is to auto-provision an API key the moment a user signs up, so when they land in the portal, the key is already there. No manual step. No support ticket.
This tutorial shows you how to wire Supabase Auth to Zuplo via a Supabase edge function. When a user signs up, the Supabase Auth hook fires an edge function that calls the Zuplo Developer API and creates a consumer and an API key. All before the user hits their first page.
This tutorial assumes you already have a Zuplo project with API key auth configured. If you don’t, this tutorial walks you through that first.
What this doesn’t cover: in-app key management, rotation, and revocation. Zuplo’s developer portal handles all of that out of the box; you won’t need to build it.
Looking to monetize your API? If you need Stripe subscriptions, plan upgrades, usage metering, and billing tied to API keys, Zuplo's Monetization feature handles all of that as an integrated system. This tutorial is for teams who want API key auto-creation on signup without full monetisation. If that's you, read on.
Section 1: How It Works
Before writing any code, it’s worth understanding what fires when. Because picking the wrong Supabase hook is the most common reason this setup silently fails.
Here’s the full sequence:
- A user signs up in the developer portal
- Supabase fires an Auth hook
- The hook calls your edge function
- The edge function calls the Zuplo Developer API, which creates an API consumer and key
- The user lands in the portal with their API key already configured
Section 2: Setting Up Supabase
Supabase handles your user auth: sign up, sign in, and email verification. If you already have a Supabase project, you only need to grab two values from it and check one setting.
If you’re starting fresh, go to Supabase and create a new project. Once your project is ready, copy the project URL and publishable (anon) key from the project settings.
The email/password auth method is enabled by default.
If you want users to verify their emails before signing in, enable email confirmation.
Note: With email confirmation enabled, you'll need to configure a redirect callback so Supabase knows where to send users after they verify their email. You do this in
zudoku.config.tsxusing theredirectAfterSignupfield in the authentication config. Zuplo's Supabase auth docs cover this. For this tutorial, email confirmation is turned off to keep things simple.
Section 3: Configuring the Developer Portal
The developer portal’s behaviour is controlled by zudoku.config.tsx, which lives in the docs folder of your Zuplo project. This is where you tell the portal which auth provider to use, in this case, Supabase.
Go to the Code tab, open zudoku.config.tsx and replace the authentication field with the following:
authentication: {
type: "supabase",
providers: [],
supabaseUrl: "your-supabase-url",
supabaseKey: "your-publishable-key",
}
Let’s break down what’s happening in this Zuplo authentication configuration code:
- You configured authentication in the
zudoku.config.tsxfile by setting thetypeto “supabase”, which tells Zuplo to use Supabase as your authentication provider. This enables user management, login flows, and JWT validation through Supabase’s auth system. - The
supabaseUrlpoints to your specific Supabase project endpoint, andsupabaseKeyis the publishable/anonymous key that allows the gateway to verify JWTs and communicate with Supabase Auth without exposing admin credentials. - The
providersarray is empty, meaning only Supabase’s default email/password authentication is enabled. But you could add social providers like Google or GitHub by populating this array (not included in this tutorial).
This authentication configuration integrates Supabase Auth directly into your API gateway, automatically protecting routes, validating tokens, and attaching user information to incoming requests without writing any custom authentication logic.
Replace the placeholder values with the project URL and publishable key you copied in Section 2.
Click Save. Zuplo automatically commits the change, rebuilds the gateway, and redeploys the portal. You don't need to push anything manually; the portal will reflect the new auth configuration within a few seconds.
Section 4: Creating the Edge Function
The edge function is the bridge between Supabase and Zuplo. When the Supabase Auth hook fires, it calls this function with the new user’s data. The function takes that data, calls the Zuplo Developer API, and creates a consumer with an API key attached—all in one round trip.
In your Supabase project dashboard, navigate to the Edge Functions tab.
Click 'Deploy a new function’ and select 'Via Editor’ (because you’ll write the edge function directly in the Supabase editor) to create a new edge function.
Replace the generated code with the following:
import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0";
import { randomUUID } from "node:crypto";
Deno.serve(async (req: Request) => {
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const WEBHOOK_SECRET = Deno.env.get("BEFORE_USER_CREATED_SECRET");
if (!WEBHOOK_SECRET) {
console.error("Missing BEFORE_USER_CREATED_SECRET");
return new Response(JSON.stringify({}), {
status: 200,
headers: { "content-type": "application/json" },
});
}
const payload = await req.text();
const headers = Object.fromEntries(req.headers);
const wh = new Webhook(WEBHOOK_SECRET.replace("v1,whsec_", ""));
let event: any;
try {
event = wh.verify(payload, headers);
} catch (err) {
console.error("Webhook verification failed:", err);
return new Response("Unauthorized", { status: 401 });
}
const user = event.user;
try {
const ZUPLO_API_KEY = Deno.env.get("ZUPLO_API_KEY");
if (!ZUPLO_API_KEY) throw new Error("Missing ZUPLO_API_KEY");
const ZUPLO_ACCOUNT = Deno.env.get("ZUPLO_ACCOUNT_NAME")!;
const API_KEY_BUCKET = Deno.env.get("ZUPLO_BUCKET_NAME")!;
const consumerName = `c-${randomUUID()}`;
const response = await fetch(
`https://dev.zuplo.com/v1/accounts/${ZUPLO_ACCOUNT}/key-buckets/${API_KEY_BUCKET}/consumers?with-api-key=true`,
{
method: "POST",
headers: {
Authorization: `Bearer ${ZUPLO_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
description: `Consumer for ${user.email ?? ""}`,
managers: [user.email],
metadata: { user_id: user.id },
name: consumerName,
}),
}
);
const result = await response.json().catch(() => null);
if (!response.ok) {
console.error("Zuplo error:", result);
} else {
console.log(`Consumer created on signup: ${result?.id} for user ${user.id}`);
}
} catch (err) {
// Don't block user creation
console.error("Provisioning failed:", err);
}
// Before User Created expects an empty object back
return new Response(JSON.stringify({}), {
status: 200,
headers: { "content-type": "application/json" },
});
});
Let’s break down what’s happening in this Supabase Edge Function code:
- You defined a Supabase webhook handler that triggers before a new user is created in your authentication system. The function first validates that the request uses the POST method and verifies the incoming webhook signature using a Standard Webhooks library with a secret from environment variables.
- Upon successful verification, the function extracts the new user’s email and ID from the verified event payload. It then calls the Zuplo API to automatically provision a new API consumer (and key) for this user, generating a unique consumer name with a random UUID and setting the user’s email as a manager on the consumer.
- The function includes careful error handling that logs failures but never blocks user creation—critical for maintaining a good signup experience. Regardless of whether Zuplo provisioning succeeds or fails, the webhook always returns an empty JSON object with a 200 status, confirming to Supabase that user creation should proceed.
This edge function creates seamless API key provisioning: when a user signs up via Supabase Auth, Zuplo automatically gets an API consumer ready for them behind the scenes, all without disrupting the user’s signup flow.
Adding the Secrets
The edge function needs three values now and one more after the next section:
- Zuplo API key — authenticates your calls to the Zuplo Developer API
- Zuplo organization name — scopes the API consumer to your org
- Bucket Name — tells Zuplo which API key service to create the consumer in
-
BEFORE_USER_CREATED_SECRET: secret key generated when you create your Supabase Auth hook, which you’ll do in the next section.
Here’s where to find each one:
Zuplo API key
Click your avatar/logo → Settings → API Key. If you haven’t created one before, generate a new key and copy it.
Zuplo organization name
This is the name you chose when creating your Zuplo organization.
Bucket Name for the API key service
Go to the Services tab and select your API key service. Click Bucket Details to see the bucket name.
Important: Make sure you select the right environment. A consumer created with a development bucket name will only appear in the development developer portal, not in production. (More on this in the Testing section.)
Adding the secrets to the Supabase edge function
In your edge function settings, go to Secrets and add variables with the values you copied.
Click “Deploy function”.
Your edge function is ready. Copy the function URL; you’ll need it in the next step.
Section 5: Wiring the Hook in Supabase
The edge function exists, but nothing is calling it yet. The Supabase Auth hook connects a new signup event to your function.
In your Supabase project dashboard, navigate to Authentication → Auth Hooks.
Note: At the time of writing, the Auth Hooks feature is in beta, but it works reliably for this use case.
Click Add new hook and select Before User Created.
Before User Createdis the right hook here because it fires exactly once—when a new user is created.Custom Access Tokenfires on every login, which means you'd be attempting to provision a consumer on every sign-in, not just the first time.
Select HTTPS as the hook type and paste in your edge function URL.
Click Generate new secret key. This secret prevents unauthorized callers from hitting your hook endpoint directly. Copy the secret key and save the hook.
Adding the Secret to the Edge Function
Go back to your edge function. Add a new secret named BEFORE_USER_CREATED_SECRET and set its value to the secret key you just generated.
The full integration is now wired together. Supabase will call your edge function on every new signup, and the function has everything it needs to create a Zuplo consumer.
Section 6: Testing It End to End
Open your developer portal. Which URL to use depends on which bucket name you configured in the edge function:
- Production bucket: Click Deployment URL and select Production
- Development bucket: Click 'Deployment URL’ and select 'Working copy’
Click Login and select Sign up. If you already have a user in your Supabase backend, use their credentials to sign in instead.
After signing in/up, you’ll be redirected to the API Reference tab of the developer portal.
To verify the key was provisioned, click My Account → API Keys. You’ll see the key already attached to your account—no manual step required.
Better yet: when a user wants to test an API route in the portal, their key is pre-selected. They choose the consumer and click Send.
Troubleshooting
Below are the most common issues you’ll hit and how to fix them.
User signs up, but no API key is created
This usually means you’re hitting the wrong developer portal for the bucket environment you configured.
- If you used a development bucket name in the edge function, select the “Working Copy" Developer Portal.
- If you used a production bucket name, select the "Production” Developer Portal.
Edge Function Returns a 401 Error
Two things to check:
Verify JWT is disabled:
In your edge function settings, turn off “Verify JWT”. The hook request doesn’t carry a Supabase JWT, so leaving this on will reject every call.
Wrong hook type selected:
Make sure you selected the Before User Created hook and set the endpoint type to HTTPS (not Postgres function). Double-check that the edge function URL is correct.
Wrong or missing BEFORE_USER_CREATED_SECRET:
The edge function verifies the webhook signature on every request. If the BEFORE_USER_CREATED_SECRET value in your edge function secrets doesn't match the secret Supabase generated when you configured the hook, every request will fail verification and return a 401.
Go back to Authentication → Auth Hooks, copy the secret again, and make sure the value in your edge function secrets matches it exactly, including the v1,whsec_ prefix.
Conclusion
Every user who signs up now gets a Zuplo consumer and API key automatically: no manual provisioning, no delay between signup and first API call.
From here, Zuplo’s developer portal handles the key management layer for free: users can roll their keys and edit their key label. For developer-facing APIs, that’s usually all you need.
If you want to go deeper—for example, tying API key creation to a billing plan or metering API usage per consumer—the next problem to solve is provisioning keys from inside your app rather than from a Supabase hook. That gives you the control to attach plan metadata at creation time and gate key generation behind a payment check. This article on the Zuplo blog covers in-app key management if you want to go that route.





















