Create a Supabase Edge Function at supabase/functions/verify/index.ts. Read the transaction reference from the URL query string, call GET https://api.paystack.co/transaction/verify/:reference with your PAYSTACK_SECRET_KEY from Deno.env, check data.status === "success", update your Supabase database with the Supabase client, and return the result.
// supabase/functions/verify/index.ts
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
const url = new URL(req.url);
const reference = url.searchParams.get("reference");
if (!reference) {
return new Response(
JSON.stringify({ error: "Reference is required" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const secretKey = Deno.env.get("PAYSTACK_SECRET_KEY");
// Call Paystack verify
const paystackResponse = await fetch(
"https://api.paystack.co/transaction/verify/" + encodeURIComponent(reference),
{ headers: { Authorization: "Bearer " + secretKey } }
);
const paystackData = await paystackResponse.json();
if (!paystackData.status || paystackData.data.status !== "success") {
return new Response(
JSON.stringify({
verified: false,
status: paystackData.data?.status,
message: paystackData.data?.gateway_response || "Payment not verified",
}),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const txData = paystackData.data;
// Update database using service role key (bypasses RLS)
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
const { error: dbError } = await supabase
.from("orders")
.update({
status: "paid",
paid_at: new Date().toISOString(),
amount_paid: txData.amount / 100,
currency: txData.currency,
})
.eq("reference", reference);
if (dbError) {
console.error("DB update failed:", dbError.message);
}
return new Response(
JSON.stringify({
verified: true,
amount: txData.amount / 100,
currency: txData.currency,
reference: txData.reference,
customer_email: txData.customer.email,
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
});
supabase functions deploy verify
// Frontend call
const { data, error } = await supabase.functions.invoke('verify', {
method: 'GET',
// or pass as query param:
})
// Or direct fetch:
var response = await fetch(
process.env.SUPABASE_URL + '/functions/v1/verify?reference=' + reference,
{
headers: { 'Authorization': 'Bearer ' + process.env.SUPABASE_ANON_KEY }
}
)
var result = await response.json()
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
This guide is part of the Paystack integration guides by language and framework series.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course