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. Accept Payments with Paystack in Nuxt
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Accept Payments with Paystack in Nuxt

To accept Paystack payments in Nuxt, create a server route at server/api/pay.post.ts that initializes transactions using your secret key stored in runtimeConfig. Your Nuxt page loads Paystack Inline.js, calls the server route, and opens the popup with the access code. After payment, another server route verifies the transaction.

In this article

  1. 1. Why Nuxt for Paystack
  2. 2. Project Setup
  3. 3. Server Route: Initialize Transaction
  4. 4. Inline Popup Checkout
  5. 5. Server Route: Verify Transaction
  6. 6. Redirect Checkout
  7. 7. Production Checklist
  8. 8. Ship Payments Faster

Why Nuxt for Paystack

Nuxt is the full-stack framework for Vue. Unlike a plain Vue SPA, Nuxt gives you server routes where you can safely store your Paystack secret key and make API calls to Paystack. You do not need a separate Express, Django, or Laravel backend. Everything lives in one project.

Nuxt server routes run on the server during both SSR and as API endpoints. When you create a file at server/api/pay.post.ts, Nuxt exposes it at /api/pay as a POST endpoint. Your Vue pages call these endpoints with $fetch or useFetch.

Project Setup

Add your Paystack keys to the environment and Nuxt config:

# .env
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxx
PAYSTACK_PUBLIC_KEY=pk_test_xxxxxxxxxxxx
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    paystackSecretKey: process.env.PAYSTACK_SECRET_KEY,
    public: {
      paystackPublicKey: process.env.PAYSTACK_PUBLIC_KEY,
    },
  },
});

The paystackSecretKey is only available in server routes via useRuntimeConfig(). It never reaches the browser. The public key is safe for the frontend.

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Server Route: Initialize Transaction

// server/api/pay.post.ts
export default defineEventHandler(async (event) => {
  var config = useRuntimeConfig();
  var body = await readBody(event);
  var email = body.email;
  var amount = body.amount;

  var reference = 'nuxt_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);

  var response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + config.paystackSecretKey,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email: email,
      amount: Math.round(amount * 100),
      reference: reference,
    }),
  });

  var data = await response.json();

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

  return {
    access_code: data.data.access_code,
    authorization_url: data.data.authorization_url,
    reference: data.data.reference,
  };
});

This runs on the server. The secret key never leaves the server environment.

Inline Popup Checkout

Create a checkout page that loads Paystack Inline.js and opens the popup:

<!-- pages/checkout.vue -->
<script setup>
useHead({
  script: [{ src: 'https://js.paystack.co/v2/inline.js', defer: true }],
});

var email = ref('');
var loading = ref(false);
var amount = 5000; // 5,000 NGN

async function handlePay() {
  if (!email.value) return;
  loading.value = true;

  try {
    var data = await $fetch('/api/pay', {
      method: 'POST',
      body: { email: email.value, amount: amount },
    });

    var popup = new window.PaystackPop();
    popup.checkout({
      accessCode: data.access_code,
      onSuccess: async function(transaction) {
        var result = await $fetch('/api/verify', {
          params: { reference: transaction.reference },
        });

        if (result.verified) {
          navigateTo('/payment/success?ref=' + transaction.reference);
        }
        loading.value = false;
      },
      onCancel: function() {
        loading.value = false;
      },
    });
  } catch (err) {
    alert('Payment initialization failed');
    loading.value = false;
  }
}
</script>

<template>
  <div>
    <h1>Checkout</h1>
    <input v-model="email" type="email" placeholder="Email address" />
    <p>Amount: NGN {{ amount.toLocaleString() }}</p>
    <button @click="handlePay" :disabled="loading || !email">
      {{ loading ? 'Processing...' : 'Pay Now' }}
    </button>
  </div>
</template>

Nuxt's $fetch calls your server routes directly. No need to configure CORS or specify a separate API URL. The useHead composable loads the Paystack script.

Server Route: Verify Transaction

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

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

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

  var data = await response.json();

  if (data.status && data.data.status === 'success') {
    // Update order in database
    return {
      verified: true,
      amount: data.data.amount / 100,
      currency: data.data.currency,
      reference: data.data.reference,
    };
  }

  throw createError({ statusCode: 400, message: 'Payment not verified' });
});

Redirect Checkout

For redirect checkout, send the customer to the authorization URL and handle the callback:

async function handleRedirectPay() {
  var data = await $fetch('/api/pay', {
    method: 'POST',
    body: { email: email.value, amount: amount },
  });

  if (data.authorization_url) {
    window.location.href = data.authorization_url;
  }
}

Set the callback URL in your initialization to point to a Nuxt page:

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

var { data, error } = await useFetch('/api/verify', {
  params: { reference: route.query.reference || route.query.trxref },
});
</script>

<template>
  <div v-if="data">
    <h1>Payment Successful</h1>
    <p>{{ data.currency }} {{ data.amount }}</p>
  </div>
  <div v-else-if="error">
    <p>Verification failed. Please contact support.</p>
  </div>
  <div v-else>
    <p>Verifying payment...</p>
  </div>
</template>

With Nuxt SSR, useFetch runs the verification on the server during page load. The customer sees the result immediately without a loading spinner on most connections.

Production Checklist

  1. Switch keys. Replace sk_test_ and pk_test_ with live keys in your production .env file.
  2. Validate amounts server-side. Look up prices from your database in the server route. Do not trust the amount sent from the page.
  3. Set up webhooks. Initialize and verify give you immediate feedback, but webhooks handle edge cases. See Handle Paystack Webhooks in Nuxt.
  4. HTTPS. Nuxt must be deployed behind HTTPS for Paystack to work in production.
  5. Idempotent fulfillment. Both the verification call and the webhook can confirm the same payment. Use a database flag to avoid double-granting.

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 3 server routes replace the need for a separate backend. Your Paystack secret key stays in runtimeConfig (server-side only).
  • ✓Create server/api/pay.post.ts to initialize transactions and server/api/verify.get.ts to verify them.
  • ✓Load Paystack Inline.js with useHead or a client-side plugin. Open the popup in your page component.
  • ✓Amounts must be in the smallest currency unit (kobo for NGN, pesewas for GHS, cents for KES/ZAR/USD).
  • ✓For redirect checkout, set the callback_url to a Nuxt page that verifies on mount.
  • ✓Store your Paystack keys in .env and access them through useRuntimeConfig().paystack_secret_key in server routes.

Frequently Asked Questions

Do I need a separate backend with Nuxt?
No. Nuxt server routes act as your backend. You create files in the server/ directory and they run on the server. Your Paystack secret key stays in runtimeConfig and never reaches the browser.
Should I use useFetch or $fetch for the payment calls?
Use $fetch inside event handlers (button clicks) because it runs on the client. Use useFetch in setup for data that should load with the page (like the callback verification page). useFetch also supports SSR.
Can I use the Paystack inline popup with Nuxt SSR?
Yes. The popup runs in the browser. Load the Paystack script with useHead and open the popup in a client-side event handler. SSR does not affect the popup because it only runs after user interaction.
How do I handle the Paystack public key in Nuxt?
Put it in runtimeConfig.public so it is available on both server and client. You only need the public key if you initialize the popup directly with it. When using the access_code approach (recommended), you do not need the public key on the frontend at all.

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 OTP and PIN Flows in Custom Checkout

When you use the Paystack Charge API to build a custom checkout, most card charges require the customer to enter a PIN, an OTP, or both. This guide explains exactly when each step appears, how to collect and submit the values, and how to handle the edge cases that break real-world checkouts.

Read more

Paystack Mobile Money Charge Stuck at Pending

A mobile money charge on Paystack stays "pending" for minutes or hours. The customer may have approved it, ignored it, or never received the prompt. Here is how to handle every scenario.

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.