McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Verify Paystack Payments in Nuxt
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Verify Paystack Payments in Nuxt

To verify Paystack payments in Nuxt, create a server route that calls the Paystack Verify Transaction API with your secret key from runtimeConfig. Call this route from your Vue page after the popup callback or on the redirect callback page using useFetch for SSR verification. Always check that data.data.status is "success" and that the amount matches your database record.

In this article

  1. 1. How Verification Works in Nuxt
  2. 2. Verification Server Route
  3. 3. Verify After the Inline Popup
  4. 4. Verify on the Redirect Callback Page
  5. 5. Amount Validation
  6. 6. Idempotent Fulfillment
  7. 7. Ship Payments Faster

How Verification Works in Nuxt

Paystack verification follows a simple request chain:

  1. Customer completes payment (popup or redirect).
  2. Your Nuxt page gets the transaction reference.
  3. Your page calls a Nuxt server route with the reference.
  4. The server route calls Paystack's Verify Transaction API: GET /transaction/verify/:reference.
  5. Paystack returns the transaction details including status, amount, and currency.
  6. Your server route checks the status and amount, updates the database, and returns the result to the page.

The secret key never leaves the server. The client only sees the sanitized response from your server route.

Verification Server Route

// server/api/verify.get.ts
export default defineEventHandler(async (event) => {
  var config = useRuntimeConfig();
  var query = getQuery(event);
  var reference = String(query.reference || '');

  if (!reference) {
    throw createError({ statusCode: 400, message: 'Reference is required' });
  }

  var response = await fetch(
    'https://api.paystack.co/transaction/verify/' + encodeURIComponent(reference),
    {
      headers: {
        Authorization: 'Bearer ' + config.paystackSecretKey,
      },
    }
  );

  var data = await response.json();

  if (!data.status) {
    throw createError({ statusCode: 400, message: data.message || 'Verification failed' });
  }

  var txn = data.data;

  // Look up expected amount from your database
  // var order = await db.orders.findOne({ reference: reference });
  // if (!order) throw createError({ statusCode: 404, message: 'Order not found' });
  // if (txn.amount !== order.expectedAmountInKobo) {
  //   throw createError({ statusCode: 400, message: 'Amount mismatch' });
  // }

  if (txn.status === 'success') {
    // Mark order as paid in database (idempotent)
    // await db.orders.updateOne({ reference }, { status: 'paid', paidAt: txn.paid_at });

    return {
      verified: true,
      amount: txn.amount / 100,
      currency: txn.currency,
      reference: txn.reference,
      channel: txn.channel,
      paidAt: txn.paid_at,
    };
  }

  throw createError({
    statusCode: 400,
    message: 'Payment status: ' + txn.status,
  });
});

The route returns a clean object with only the fields the frontend needs. It does not forward the entire Paystack response, which contains sensitive data like the customer's authorization object.

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Verify After the Inline Popup

In your checkout page, call the verification route inside the popup's onSuccess callback:

<script setup>
var verifyState = ref('idle');
var verifyResult = ref(null);

async function onPaystackSuccess(transaction) {
  verifyState.value = 'verifying';

  try {
    var result = await $fetch('/api/verify', {
      params: { reference: transaction.reference },
    });

    verifyState.value = 'success';
    verifyResult.value = result;
  } catch (err) {
    verifyState.value = 'failed';
  }
}
</script>

<template>
  <div v-if="verifyState === 'verifying'">
    Verifying your payment...
  </div>
  <div v-else-if="verifyState === 'success'">
    <h2>Payment Confirmed</h2>
    <p>{{ verifyResult.currency }} {{ verifyResult.amount }}</p>
  </div>
  <div v-else-if="verifyState === 'failed'">
    <p>Could not verify. Please contact support.</p>
  </div>
</template>

Use $fetch (not useFetch) inside event handlers. $fetch runs immediately when called. useFetch is designed for setup-time data fetching.

Verify on the Redirect Callback Page

For redirect checkout, Paystack appends ?trxref=xxx&reference=xxx to your callback URL. Create a page that verifies during SSR:

<!-- pages/payment/callback.vue -->
<script setup>
var route = useRoute();
var reference = route.query.reference || route.query.trxref;

var { data, error, status } = await useFetch('/api/verify', {
  params: { reference: reference },
});
</script>

<template>
  <div v-if="status === 'pending'">
    <p>Verifying your payment...</p>
  </div>
  <div v-else-if="data">
    <h1>Payment Confirmed</h1>
    <p>Amount: {{ data.currency }} {{ data.amount }}</p>
    <p>Reference: {{ data.reference }}</p>
    <p>Paid via: {{ data.channel }}</p>
    <NuxtLink to="/">Back to Home</NuxtLink>
  </div>
  <div v-else>
    <h1>Verification Failed</h1>
    <p>We could not confirm your payment. If you were charged, please contact support with reference: {{ reference }}</p>
  </div>
</template>

Because useFetch runs during SSR, the verification happens on the server before the HTML is sent to the browser. The customer sees the result immediately. No client-side loading state needed for most users.

Amount Validation

Checking status === "success" is necessary but not sufficient. You must also verify the amount. Here is the problem scenario:

  1. Your checkout page sends amount: 50000 (500 NGN) to your server route.
  2. An attacker intercepts and changes it to amount: 100 (1 NGN).
  3. Paystack initializes a 1 NGN transaction. The attacker pays 1 NGN.
  4. Your verification route sees status: "success" and grants the product.

The fix: your server route must look up the expected price from your database, not trust the frontend. Compare the verified amount field against your stored record.

// In server/api/verify.get.ts
var order = await db.orders.findOne({ reference: reference });

if (!order) {
  throw createError({ statusCode: 404, message: 'Order not found' });
}

if (txn.amount !== order.amountInKobo || txn.currency !== order.currency) {
  // Log this as a potential fraud attempt
  throw createError({ statusCode: 400, message: 'Amount or currency mismatch' });
}

Idempotent Fulfillment

Three things can confirm the same payment:

  • The popup verification call
  • The redirect callback verification call
  • The webhook

If your fulfillment logic runs on each confirmation, the customer could get triple credit. Use a database flag to make fulfillment idempotent:

// In your verification or webhook handler
var order = await db.orders.findOne({ reference: reference });

if (order.status === 'paid') {
  // Already fulfilled, just return success
  return { verified: true, amount: order.amount };
}

// First confirmation, fulfill now
order.status = 'paid';
order.paidAt = txn.paid_at;
await order.save();
// Grant access, send email, etc.

This pattern works whether the verification call or the webhook arrives first.

Ship Payments Faster

This guide is part of the Paystack integration guides by language and framework series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Nuxt server routes handle verification securely. The Paystack secret key stays in runtimeConfig and never reaches the browser.
  • ✓The Verify Transaction API checks the definitive status of a payment. Always call it before granting value.
  • ✓Use useFetch on the redirect callback page for SSR verification. The customer sees the result instantly without a loading spinner.
  • ✓Always validate the verified amount against your database. A successful status alone is not enough.
  • ✓Both the popup verification and the webhook can confirm the same transaction. Use a database flag to prevent double fulfillment.
  • ✓Return only the fields your frontend needs from the verification response. Do not expose the full Paystack response to the client.

Frequently Asked Questions

Should I use useFetch or $fetch to verify Paystack payments in Nuxt?
Use useFetch on the callback page so verification runs during SSR. Use $fetch inside event handlers like the popup onSuccess callback, where you need immediate execution after a user action.
What if the verification call fails but the customer was actually charged?
This is why webhooks exist. Your webhook handler will receive the charge.success event and can fulfill the order independently. The customer should see a message like "We are confirming your payment" and receive an email once the webhook processes.
Can I call the Paystack Verify API from the client side in Nuxt?
No. The Verify API requires your secret key in the Authorization header. Client-side code in Nuxt is visible in the browser. Always verify through a server route.
How many times can I verify the same transaction?
As many times as you want. The Verify API is idempotent and always returns the current state of the transaction. Paystack does not charge you for verification calls. You can verify a transaction days later if needed.
What does the Paystack Verify API return for failed transactions?
The API returns data.data.status as "failed" or "abandoned" depending on what happened. A failed transaction means the customer attempted to pay but the charge was declined. An abandoned transaction means the customer opened the checkout but never completed payment.

Learn Paystack Integration

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

Not ready? Create Free Account

Keep reading

Paystack Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Verify Paystack Payments in Svelte and SvelteKit

Verify Paystack transactions in SvelteKit using server endpoints for popup verification and load functions for redirect callback verification. Covers amount validation, idempotent fulfillment, and SSR verification for instant results.

Read more

Handle Paystack Webhooks in Flask

Receive and verify Paystack webhooks in your Flask application. This guide covers accessing request.data for raw bytes, HMAC SHA-512 signature verification, processing charge.success events, and making handlers idempotent.

Read more

Build Paystack Subscriptions in ASP.NET Core

Implement Paystack recurring billing in ASP.NET Core. This guide covers creating plans, subscribing customers through transaction initialization, handling renewal webhooks with C#, and cancelling subscriptions.

Read more

Paystack Transaction Export and Reporting via API

The Paystack dashboard lets you download CSV reports manually. But when you need daily automated exports, custom financial reports, or reconciliation pipelines, you need the API. This guide covers listing transactions with filters, exporting data in bulk, and building reporting workflows that run without human intervention.

Read more

Paystack USSD Payment Not Completing

USSD payments on Paystack fail more often than card or bank transfer payments. The timeout is brutal, bank USSD systems go down frequently, and customers get confused by the flow. Here is how to handle each problem.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.