Accept Payments with Paystack in Supabase Edge Functions
Create a Supabase Edge Function at supabase/functions/pay/index.ts. Store your Paystack secret key with supabase secrets set PAYSTACK_SECRET_KEY=sk_live_xxx. In the function, use Deno.env.get to read the key, call the Paystack initialize endpoint with native fetch, and return the authorization URL to your frontend.
Setup: Create the Edge Function
# Create the function
supabase functions new pay
# Store the Paystack secret key
supabase secrets set PAYSTACK_SECRET_KEY=sk_live_xxxxxxxxxxxx
# For test mode
supabase secrets set PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxx
This creates supabase/functions/pay/index.ts.
Initialize Transaction Function
// supabase/functions/pay/index.ts
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
serve(async (req) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
try {
const secretKey = Deno.env.get("PAYSTACK_SECRET_KEY");
if (!secretKey) {
return new Response(
JSON.stringify({ error: "Paystack secret key not configured" }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const { email, amount } = await req.json();
if (!email || !amount) {
return new Response(
JSON.stringify({ error: "email and amount are required" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const reference = "edge_" + Date.now() + "_" + crypto.randomUUID().split("-")[0];
const paystackResponse = await fetch(
"https://api.paystack.co/transaction/initialize",
{
method: "POST",
headers: {
Authorization: "Bearer " + secretKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
amount: Math.round(Number(amount) * 100), // Convert to kobo
reference,
}),
}
);
const data = await paystackResponse.json();
if (!data.status) {
return new Response(
JSON.stringify({ error: data.message }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
return new Response(
JSON.stringify({
authorization_url: data.data.authorization_url,
access_code: data.data.access_code,
reference: data.data.reference,
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});
Call from Frontend
// Using Supabase JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
)
async function initializePayment(email: string, amount: number) {
const { data, error } = await supabase.functions.invoke('pay', {
body: { email, amount }
})
if (error) throw error
return data // { authorization_url, access_code, reference }
}
// Or using plain fetch:
async function initializePaymentWithFetch(email: string, amount: number) {
var response = await fetch(
process.env.SUPABASE_URL + '/functions/v1/pay',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + process.env.SUPABASE_ANON_KEY,
},
body: JSON.stringify({ email, amount }),
}
)
return response.json()
}
Deploy the Function
# Deploy to production
supabase functions deploy pay
# Test locally first
supabase functions serve pay
# Test locally with curl
curl -X POST http://localhost:54321/functions/v1/pay \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "amount": 5000}'
Learn More
This guide is part of the Paystack integration guides by language and framework series.
Key Takeaways
- ✓Supabase Edge Functions run Deno TypeScript. They have access to native fetch, crypto, and Deno.env.
- ✓Store your Paystack secret key with: supabase secrets set PAYSTACK_SECRET_KEY=sk_live_xxx
- ✓Read the secret in the function with: Deno.env.get("PAYSTACK_SECRET_KEY")
- ✓Edge Functions are deployed with: supabase functions deploy pay
- ✓Call Edge Functions from your frontend via the Supabase JS client or directly via fetch.
- ✓Add CORS headers to your Edge Function for browser clients (use cors module from JSR or set headers manually).
Frequently Asked Questions
- Can I access the Supabase database from a Paystack Edge Function?
- Yes. Use the Supabase client inside Edge Functions. Import createClient from @supabase/supabase-js. Use the service role key (SUPABASE_SERVICE_ROLE_KEY) from secrets to bypass RLS for writes after payment confirmation. Do not use the anon key for server-side database operations.
- How do I set environment variables for Paystack in Supabase Edge Functions?
- Use the Supabase CLI: supabase secrets set PAYSTACK_SECRET_KEY=sk_live_xxx. Read them in the function with Deno.env.get("PAYSTACK_SECRET_KEY"). Never put secrets in your function source code.
- What is the Deno equivalent of Node.js process.env?
- Deno.env.get("KEY_NAME"). Edge Functions run Deno, not Node.js. Use Deno.env.get() for environment variables. The syntax is slightly different but the concept is the same.
- Do Supabase Edge Functions have cold start issues?
- Edge Functions run at the edge (Deno Deploy), so cold starts are generally fast (under 50ms). For Paystack initialization, which is not latency-critical, this is acceptable. If cold starts become an issue, keep functions warm with scheduled pings.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon