Paystack Payment Callback Guide
Overview
The platform now has a complete callback system for handling Paystack payment redirects. This includes both backend processing and frontend display.
Callback Flow
1. Backend Callback Endpoint
GET /api/wallet/callback
This is a public endpoint (no authentication required) that Paystack redirects to after payment. It:
- Receives the payment reference from Paystack query parameters
- Verifies the transaction with Paystack API
- Credits the user's wallet if successful
- Redirects to frontend success/failure page
Query Parameters (from Paystack):
referenceortrxref- Payment reference
Redirects:
- Success:
${FRONTEND_URL}/payment/success?reference=...&points=...&balance=... - Failure:
${FRONTEND_URL}/payment/failed?error=...&reference=...
2. Frontend Callback Pages
Success Page (/payment/success)
Query Parameters:
reference- Payment referencepoints- Points added to walletbalance- New wallet balanceamount- Amount paid in Nairaalready_processed- If payment was already processed
Failure Page (/payment/failed)
Query Parameters:
error- Error messagereference- Payment referencestatus- Payment status from Paystackmessage- Gateway response message
3. Static HTML Callback Page
A fallback callback page is available at /public/payment-callback.html that:
- Shows a loading spinner while processing
- Displays success or error messages
- Shows payment details (reference, points, balance)
- Provides links to navigate back to wallet or retry
Configuration
Environment Variables
# Backend URL (where Paystack redirects to)
BACKEND_URL=http://localhost:3000
# Frontend URL (where users are redirected after processing)
FRONTEND_URL=http://localhost:3000
# Optional: Custom callback URL (overrides default)
PAYSTACK_CALLBACK_URL=http://localhost:3000/api/wallet/callback
Paystack Dashboard Setup
Webhook Configuration (Required)
- Go to Settings → API Keys & Webhooks
- Add webhook URL:
https://yourdomain.com/api/wallet/webhook/paystack - Select events:
charge.success(required) - Save configuration
See WEBHOOK_SETUP.md for detailed webhook setup instructions.
Callback URL (Optional)
The callback URL is automatically set when initializing payments. You can override it with:
- Environment variable:
PAYSTACK_CALLBACK_URL - Default:
${BACKEND_URL}/api/wallet/callback
Callback Scenarios
Scenario 1: Successful Payment (First Time)
- User completes payment on Paystack
- Paystack redirects to
/api/wallet/callback?reference=wallet_... - Backend verifies payment with Paystack API
- Backend credits wallet
- Backend redirects to frontend success page with details
Scenario 2: Successful Payment (Already Processed)
- User completes payment on Paystack
- Paystack redirects to
/api/wallet/callback?reference=wallet_... - Backend checks for existing transaction (idempotency)
- If already processed, redirects to success page with
already_processed=true - No duplicate credit occurs
Scenario 3: Failed Payment
- User attempts payment on Paystack
- Payment fails or is abandoned
- Paystack redirects to
/api/wallet/callback?reference=... - Backend verifies and finds status != 'success'
- Backend redirects to frontend failure page with error details
Scenario 4: Webhook Processing (Parallel)
- Paystack sends webhook to
/api/wallet/webhook/paystack - Webhook handler verifies signature and processes payment
- This happens independently of redirect callback
- Both webhook and callback are idempotent (no double-crediting)
Frontend Integration
Option 1: Use Backend Redirect (Recommended)
// When initializing payment
const response = await fetch('/api/wallet/fund', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: 1000 })
});
const data = await response.json();
// Redirect to Paystack
window.location.href = data.authorization_url;
// Backend callback will handle redirect to your success/failure pages
Option 2: Use Inline Widget + Manual Verification
// Use Paystack inline widget
const handler = PaystackPop.setup({
key: data.publicKey,
email: userEmail,
amount: data.payment.amount * 100,
ref: data.reference,
callback: function(response) {
// Verify payment manually
verifyPayment(response.reference);
}
});
async function verifyPayment(reference) {
const response = await fetch(`/api/wallet/verify/${reference}`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
const result = await response.json();
if (result.success) {
// Redirect to success page
window.location.href = `/payment/success?reference=${reference}&points=${result.transaction.amount}&balance=${result.wallet.balance}`;
}
}
Testing
Test Callback Flow
- Start server:
npm run dev - Initialize payment:
POST /api/wallet/fund - Use test card:
4084084084084081 - After payment, Paystack redirects to callback
- Check redirect URL for success/failure parameters
Test with cURL
# Simulate callback (after payment)
curl "http://localhost:3000/api/wallet/callback?reference=wallet_test_123"
Security Considerations
- Public Endpoint: The callback endpoint is public (no auth) because Paystack redirects to it
- Verification: Always verify with Paystack API (never trust redirect parameters)
- Idempotency: Both callback and webhook check for existing transactions
- Signature Verification: Webhook uses HMAC signature verification
- Error Handling: All errors redirect to frontend with safe error messages
Error Handling
The callback handler catches all errors and redirects to the failure page with:
- Error message (URL encoded)
- Reference (if available)
- Status (if available)
Frontend should display these safely and allow users to retry or contact support.