Vue cannot verify Paystack payments directly because verification requires your secret key. Build a backend endpoint that calls Paystack's Verify Transaction API, then call that endpoint from Vue after the popup callback or redirect. Check that data.data.status equals "success" and that the amount matches what you expected.
When a customer finishes paying through the Paystack popup, your Vue component receives a callback with the transaction reference. That callback runs in the browser. It tells you the customer completed the payment form, but it does not prove the money actually landed. A user could fake the callback, or the network could drop before Paystack finished processing.
The Paystack Verify Transaction API (GET /transaction/verify/:reference) is the only reliable way to confirm a payment. It requires your secret key in the Authorization header. That key must never appear in client-side code. So your Vue app calls your own backend, and your backend calls Paystack.
This guide uses Express for the backend examples, but the pattern is the same for Django, Laravel, FastAPI, or any server framework. The Vue side stays identical regardless of backend choice.
Create a simple endpoint that takes a reference, calls Paystack, and returns the result:
// server.js (Express)
const express = require('express');
const app = express();
const PAYSTACK_SECRET = process.env.PAYSTACK_SECRET_KEY;
app.get('/api/verify', async (req, res) => {
var reference = req.query.reference;
if (!reference) {
return res.status(400).json({ error: 'Reference is required' });
}
var response = await fetch(
'https://api.paystack.co/transaction/verify/' + encodeURIComponent(reference),
{
headers: { Authorization: 'Bearer ' + PAYSTACK_SECRET },
}
);
var data = await response.json();
if (!data.status) {
return res.status(400).json({ verified: false, message: data.message });
}
var txn = data.data;
// Look up the expected amount from your database
// var order = await db.orders.findOne({ reference: reference });
// if (txn.amount !== order.expectedAmountInKobo) { return res.status(400).json({ verified: false }); }
if (txn.status === 'success') {
return res.json({
verified: true,
amount: txn.amount / 100,
currency: txn.currency,
reference: txn.reference,
channel: txn.channel,
paidAt: txn.paid_at,
});
}
res.status(400).json({ verified: false, status: txn.status });
});
app.listen(4000);
The endpoint does three things: calls Paystack, checks the status, and validates the amount. The amount check (commented out above) is critical in production. Without it, a customer could initialize a transaction for 100 NGN and you would verify a 100 NGN payment when they should have paid 10,000 NGN.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
In your Vue component, call your backend verification endpoint inside the popup's onSuccess callback:
<script setup>
import { ref } from 'vue';
var API_URL = import.meta.env.VITE_API_URL || 'http://localhost:4000';
var verifyState = ref('idle'); // idle | verifying | success | failed
var verifyResult = ref(null);
async function onPaystackSuccess(transaction) {
verifyState.value = 'verifying';
try {
var res = await fetch(
API_URL + '/api/verify?reference=' + transaction.reference
);
var data = await res.json();
if (data.verified) {
verifyState.value = 'success';
verifyResult.value = data;
} else {
verifyState.value = 'failed';
}
} catch (err) {
verifyState.value = 'failed';
}
}
</script>
<template>
<div v-if="verifyState === 'verifying'">
<p>Verifying your payment...</p>
</div>
<div v-else-if="verifyState === 'success'">
<h2>Payment Confirmed</h2>
<p>{{ verifyResult.currency }} {{ verifyResult.amount }} received.</p>
<p>Reference: {{ verifyResult.reference }}</p>
</div>
<div v-else-if="verifyState === 'failed'">
<p>Verification failed. Contact support with your reference number.</p>
</div>
</template>
The verifyState ref drives the UI. The user sees a loading indicator during verification, a confirmation on success, and a clear error message on failure. Keep the reference visible so the customer can share it with support if needed.
When you use redirect checkout instead of the inline popup, Paystack sends the customer back to your callback URL with ?trxref=xxx&reference=xxx in the query string. Create a callback page in Vue Router that verifies on mount:
<!-- src/views/PaymentCallback.vue -->
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
var route = useRoute();
var router = useRouter();
var API_URL = import.meta.env.VITE_API_URL || 'http://localhost:4000';
var status = ref('verifying');
var result = ref(null);
onMounted(async function() {
var reference = route.query.reference || route.query.trxref;
if (!reference) {
status.value = 'error';
return;
}
try {
var res = await fetch(
API_URL + '/api/verify?reference=' + reference
);
var data = await res.json();
if (data.verified) {
status.value = 'success';
result.value = data;
} else {
status.value = 'failed';
}
} catch (err) {
status.value = 'error';
}
});
</script>
<template>
<div v-if="status === 'verifying'">Verifying payment...</div>
<div v-else-if="status === 'success'">
<h1>Payment Successful</h1>
<p>{{ result.currency }} {{ result.amount }}</p>
</div>
<div v-else>
<p>Something went wrong. Please contact support.</p>
</div>
</template>
Register this route in your Vue Router config:
// router/index.js
{ path: '/payment/callback', component: () => import('../views/PaymentCallback.vue') }
Set this URL as the callback_url when initializing the transaction on your backend.
If you verify payments in multiple places, extract the logic:
// src/composables/usePaystackVerify.js
import { ref } from 'vue';
var API_URL = import.meta.env.VITE_API_URL || 'http://localhost:4000';
export function usePaystackVerify() {
var loading = ref(false);
var verified = ref(false);
var error = ref(null);
var transaction = ref(null);
async function verify(reference) {
loading.value = true;
error.value = null;
try {
var res = await fetch(
API_URL + '/api/verify?reference=' + reference
);
var data = await res.json();
if (data.verified) {
verified.value = true;
transaction.value = data;
} else {
error.value = 'Payment not confirmed';
}
} catch (err) {
error.value = 'Network error during verification';
} finally {
loading.value = false;
}
}
return { verify, loading, verified, error, transaction };
}
Use it in any component with a single line: var { verify, loading, verified, transaction } = usePaystackVerify();
Verifying that status === "success" is necessary but not sufficient. You must also check that the amount matches what you charged. Here is why:
amount: 10000 (100 NGN in kobo) to your backend.amount: 100 (1 NGN).The fix: your backend should look up the correct price from your database, not from the frontend request. When verifying, compare data.data.amount against the expected amount in your database.
// In your verify endpoint
var txn = data.data;
var order = await db.orders.findOne({ reference: reference });
if (txn.status === 'success' && txn.amount === order.amountInKobo) {
order.status = 'paid';
await order.save();
return res.json({ verified: true, amount: txn.amount / 100 });
}
return res.status(400).json({ verified: false, reason: 'Amount mismatch or payment not successful' });
Frontend verification can fail. The customer might close their browser after paying, or your backend could be temporarily down when the popup callback fires. Webhooks solve this.
Paystack sends a charge.success webhook to your backend independently of the frontend flow. Your backend should be able to fulfill orders from either the verification call or the webhook, whichever arrives first. Use a database flag to prevent double fulfillment.
Vue cannot receive webhooks because it runs in the browser. See Handle Paystack Webhooks in Vue for the architectural explanation and links to backend webhook guides.
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