Paystack Transaction Export and Reporting via API
Paystack provides a Transaction List endpoint to query transactions with filters (date range, status, customer, amount) and a Transaction Export endpoint to generate downloadable CSV files. Together they let you build automated reporting pipelines that pull transaction data daily, reconcile against your internal records, and feed financial systems.
Listing Transactions with Filters
The transaction list endpoint is your primary tool for querying payment data. It returns an array of transaction objects with full details.
// List successful transactions from the past 7 days
const sevenDaysAgo = new Date(Date.now() - 7 * 86400000).toISOString();
const now = new Date().toISOString();
const response = await fetch(
'https://api.paystack.co/transaction?from=' + sevenDaysAgo +
'&to=' + now +
'&status=success' +
'&perPage=100' +
'&page=1',
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const data = await response.json();
console.log('Total transactions:', data.meta.total);
console.log('Page:', data.meta.page, 'of', data.meta.pageCount);
for (const tx of data.data) {
console.log(
tx.reference + ' | ' +
tx.customer.email + ' | ' +
(tx.amount / 100) + ' ' + tx.currency + ' | ' +
tx.status
);
}
Available filter parameters:
- from / to: Date range in ISO format. Always set these to avoid pulling your entire transaction history.
- status: "success", "failed", or "abandoned". Filter by payment outcome.
- customer: Customer ID to see all transactions for a specific customer.
- amount: Filter by exact amount (in kobo/pesewas). Useful for finding a specific payment.
- perPage: Number of results per page (default 50, max 200).
- page: Page number for pagination.
For reporting, always paginate through all results. The meta object in the response tells you the total count and page count. Loop through pages until you have all records.
Paginating Through Large Result Sets
If you process hundreds or thousands of transactions daily, a single API call will not return everything. You need to paginate.
async function getAllTransactions(from, to, status) {
let allTransactions = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(
'https://api.paystack.co/transaction?from=' + from +
'&to=' + to +
'&status=' + status +
'&perPage=200' +
'&page=' + page,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const data = await response.json();
allTransactions = allTransactions.concat(data.data);
if (page >= data.meta.pageCount) {
hasMore = false;
} else {
page++;
}
}
return allTransactions;
}
// Usage
const transactions = await getAllTransactions('2026-07-21', '2026-07-22', 'success');
console.log('Total fetched:', transactions.length);
Set perPage to 200 (the maximum) to minimize the number of API calls. Be mindful of rate limits. If you are pulling data for a long date range with many transactions, add a small delay between page requests to avoid hitting rate limits.
Store the fetched data in your own database or data warehouse. Do not query Paystack in real-time for every dashboard view or report request. Pull the data periodically and serve reports from your own store.
The Transaction Export Endpoint
For bulk data pulls (monthly reports, tax filings, audits), the export endpoint generates a CSV file with all matching transactions.
// Request a transaction export
const response = await fetch('https://api.paystack.co/transaction/export', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
// data.data.path = URL to download the CSV file
console.log('Download URL:', data.data.path);
You can filter the export with the same parameters as the list endpoint: from, to, status, customer, etc. Append them as query parameters.
// Export only successful transactions for July 2026
const exportUrl = 'https://api.paystack.co/transaction/export?from=2026-07-01&to=2026-07-31&status=success';
const response = await fetch(exportUrl, {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
// Download the CSV from data.data.path
The export is generated asynchronously. For small datasets, the URL may be available immediately. For large datasets (thousands of transactions), generation takes a few minutes. The download URL is temporary. Download the file promptly and store it in your own storage.
The CSV contains columns for reference, amount, currency, status, customer email, payment channel, fees, created date, and other transaction fields. The exact columns may vary as Paystack updates the export format.
Building an Automated Daily Report
Here is a complete daily reporting pipeline that pulls the previous day's transactions, summarizes them, and stores the report.
async function dailyTransactionReport() {
const yesterday = new Date(Date.now() - 86400000);
const from = yesterday.toISOString().split('T')[0];
const to = new Date().toISOString().split('T')[0];
// Pull all successful transactions
const successful = await getAllTransactions(from, to, 'success');
// Pull all failed transactions
const failed = await getAllTransactions(from, to, 'failed');
// Calculate summary
const totalRevenue = successful.reduce(function(sum, tx) { return sum + tx.amount; }, 0);
const totalFees = successful.reduce(function(sum, tx) { return sum + (tx.fees || 0); }, 0);
const totalNet = totalRevenue - totalFees;
// Group by payment channel
const byChannel = {};
for (const tx of successful) {
const channel = tx.channel || 'unknown';
if (!byChannel[channel]) {
byChannel[channel] = { count: 0, amount: 0 };
}
byChannel[channel].count++;
byChannel[channel].amount += tx.amount;
}
const report = {
date: from,
successful_count: successful.length,
failed_count: failed.length,
total_revenue_kobo: totalRevenue,
total_fees_kobo: totalFees,
total_net_kobo: totalNet,
by_channel: byChannel,
};
// Store in database
await db.query(
'INSERT INTO daily_reports (report_date, data) VALUES ($1, $2)',
[from, JSON.stringify(report)]
);
console.log('Daily report for ' + from + ':');
console.log(' Successful: ' + successful.length + ' transactions');
console.log(' Failed: ' + failed.length + ' transactions');
console.log(' Revenue: ' + (totalRevenue / 100).toLocaleString() + ' NGN');
console.log(' Fees: ' + (totalFees / 100).toLocaleString() + ' NGN');
console.log(' Net: ' + (totalNet / 100).toLocaleString() + ' NGN');
return report;
}
Schedule this function to run every morning via a cron job or a scheduled cloud function. It gives your finance team a daily summary without them needing to log into the Paystack dashboard.
Reconciling Transactions Against Your Orders
The most important reporting task is making sure every Paystack transaction matches an order in your system. Here is a reconciliation approach:
async function reconcileTransactions(from, to) {
const paystackTxns = await getAllTransactions(from, to, 'success');
const mismatches = [];
for (const tx of paystackTxns) {
// Look up the order in our database
const order = await db.query(
'SELECT id, amount, status FROM orders WHERE paystack_reference = $1',
[tx.reference]
);
if (order.rows.length === 0) {
mismatches.push({
type: 'no_order',
reference: tx.reference,
amount: tx.amount,
message: 'Paystack transaction exists but no matching order found',
});
continue;
}
const dbOrder = order.rows[0];
// Check amount match
if (dbOrder.amount !== tx.amount) {
mismatches.push({
type: 'amount_mismatch',
reference: tx.reference,
paystack_amount: tx.amount,
order_amount: dbOrder.amount,
message: 'Amount mismatch between Paystack and order record',
});
}
// Check status match
if (dbOrder.status !== 'paid' && dbOrder.status !== 'settled') {
mismatches.push({
type: 'status_mismatch',
reference: tx.reference,
order_status: dbOrder.status,
message: 'Paystack shows success but order is not marked as paid',
});
}
}
if (mismatches.length > 0) {
console.log('Found ' + mismatches.length + ' mismatches:');
for (const m of mismatches) {
console.log(' ' + m.type + ': ' + m.reference + ' - ' + m.message);
}
// Send alert to finance team
} else {
console.log('All ' + paystackTxns.length + ' transactions reconciled successfully');
}
return mismatches;
}
Run this alongside your daily report. It catches orders that were paid on Paystack but not recorded in your system (missed webhooks), amount mismatches (partial payments or bugs), and status inconsistencies.
Using Metadata for Richer Reports
If you pass metadata when initializing transactions, that metadata appears in the transaction list and export. This opens up powerful reporting dimensions.
// When creating the transaction, include metadata
const initResponse = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'customer@example.com',
amount: 500000,
metadata: {
product_type: 'subscription',
plan: 'pro',
campaign: 'july_promo',
referral_source: 'twitter',
},
}),
});
// Later, in your report, filter or group by metadata
const transactions = await getAllTransactions('2026-07-01', '2026-07-31', 'success');
const byCampaign = {};
for (const tx of transactions) {
const campaign = (tx.metadata && tx.metadata.campaign) || 'organic';
if (!byCampaign[campaign]) {
byCampaign[campaign] = { count: 0, revenue: 0 };
}
byCampaign[campaign].count++;
byCampaign[campaign].revenue += tx.amount;
}
console.log('Revenue by campaign:', JSON.stringify(byCampaign, null, 2));
Plan your metadata schema before launch. Consistent metadata keys across all transactions make reporting straightforward. Inconsistent or missing metadata makes it impossible to slice data later. See metadata guide for schema design patterns.
Key Takeaways
- ✓The Transaction List endpoint (/transaction) returns paginated transaction records with support for filtering by date, status, customer, and amount. Use it for real-time queries and dashboard displays.
- ✓The Transaction Export endpoint (/transaction/export) generates a CSV file containing all transactions matching your filters. Paystack emails you a download link or returns a URL. Use it for bulk data pulls and accounting system imports.
- ✓Always filter by date range when listing transactions. Without a date filter, the API returns your most recent transactions across all time, which may not be what you need for reporting.
- ✓Transaction amounts in the API are in the smallest currency unit (kobo, pesewas, cents). Divide by 100 before displaying or importing into accounting software.
- ✓Build automated daily exports as a cron job. Pull the previous day's transactions, store them in your reporting database, and flag any that do not match your internal order records.
- ✓The export endpoint is asynchronous. You request the export, Paystack generates the file, and you get a download URL. For large datasets, generation may take a few minutes.
Frequently Asked Questions
- How far back can I query Paystack transaction data?
- The Paystack Transaction List API does not have a documented time limit on historical queries. You can query transactions from the day you created your Paystack account. However, pulling very long date ranges without pagination will be slow. Always paginate and set reasonable date ranges for your queries.
- What format does the Paystack transaction export use?
- The export generates a CSV (comma-separated values) file. It includes columns for reference, amount, currency, status, customer email, payment channel, fees, and timestamps. You can import this CSV into Excel, Google Sheets, or any accounting software that supports CSV import.
- Are transaction fees included in the transaction list response?
- Yes. Each transaction object in the list response includes a fees field that shows the processing fee Paystack charged for that specific transaction. The fee is in the smallest currency unit (kobo, pesewas, cents). The net amount you receive is the transaction amount minus the fee.
- Can I export failed and abandoned transactions?
- Yes. Use the status filter parameter with "failed" or "abandoned" to export those transaction types. This is useful for analyzing payment failure rates, understanding why customers abandon checkout, and identifying issues with specific card types or banks.
- Is there a rate limit on the transaction list endpoint?
- Paystack applies rate limits to all API endpoints. The exact limits are not publicly documented and may vary by account. If you are building automated reporting, add a small delay (1 to 2 seconds) between paginated requests to stay well within limits. If you hit rate limits, the API returns a 429 status code. Back off and retry after a few seconds.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon