Paystack Transaction Timeline API for Debugging Customer Complaints
The Paystack Transaction Timeline API returns a chronological list of events for a single transaction. Each event includes a timestamp, action description, and relevant data. You can see when the transaction was initialized, when the customer entered card details, when authorization was sent to the bank, whether 3DS was triggered, when the charge succeeded or failed, and when the webhook was sent.
The Transaction Timeline Endpoint
The timeline endpoint gives you a step-by-step record of everything that happened during a transaction. You can query it by transaction ID or reference.
// Fetch timeline by transaction reference
const reference = 'order_142_1721472000000';
const response = await fetch(
'https://api.paystack.co/transaction/timeline/' + reference,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const data = await response.json();
// data.data.history is an array of timeline events
// data.data.start_time is when the transaction started
for (const event of data.data.history) {
console.log(event.time + ' | ' + event.type + ' | ' + event.message);
}
Each event in the history array contains:
- type: The category of event (e.g., "action", "success", "close", "error")
- message: A human-readable description of what happened
- time: Seconds elapsed since the transaction started
The timeline is available for all transaction types: card, bank transfer, USSD, and mobile money. The events differ by channel, but the structure is the same.
Reading Timeline Events
Here is what a typical successful card payment timeline looks like:
// Example timeline for a successful card payment
[
{ "type": "action", "message": "Attempted to charge card", "time": 0 },
{ "type": "action", "message": "Authentication Required: OTP", "time": 1 },
{ "type": "action", "message": "OTP submitted", "time": 45 },
{ "type": "success", "message": "Transaction Successful", "time": 47 },
{ "type": "action", "message": "Webhook sent to https://yoursite.com/webhooks/paystack", "time": 48 },
{ "type": "action", "message": "Webhook acknowledged (200 OK)", "time": 49 }
]
And here is a failed payment timeline:
// Example timeline for a failed card payment
[
{ "type": "action", "message": "Attempted to charge card", "time": 0 },
{ "type": "action", "message": "Authentication Required: OTP", "time": 1 },
{ "type": "action", "message": "OTP submitted", "time": 120 },
{ "type": "close", "message": "Transaction Failed: Incorrect OTP", "time": 122 }
]
From the first timeline, you can see the payment completed in about 49 seconds, the customer took 45 seconds to enter their OTP, and the webhook was delivered successfully.
From the second timeline, you can see the customer took 2 minutes to enter the OTP and entered the wrong one. The charge failed because of incorrect OTP, not insufficient funds or a card problem.
This level of detail is what separates guesswork from informed debugging.
Debugging Common Customer Complaints
Here are the most common customer complaints and how to use the timeline to investigate them.
"I was charged but did not receive my product"
Pull the timeline. Check if the transaction succeeded (look for a "Transaction Successful" event). If it did, check the webhook events. Was the webhook sent? Was it acknowledged with a 200 status? If the webhook was not acknowledged, your server did not process the payment notification. Check your webhook handler logs.
"My payment failed but I was debited"
Pull the timeline. Look at the last event. If it says "Transaction Failed" with a specific reason, the charge was declined by the bank. Some banks show a temporary debit that gets reversed within 24 to 48 hours. Explain this to the customer. If the timeline shows "Transaction Successful" but your system shows it as failed, the issue is in your verification or webhook handling.
"I was charged twice"
Check your database for multiple transactions from the same customer around the same time. Pull the timeline for each transaction reference. If both timelines show "Transaction Successful," the customer may have clicked the pay button twice and initiated two separate charges. Check whether both references were generated by your system (a double-submit bug) or if one was a retry initiated by the customer.
"The payment just hung and nothing happened"
Pull the timeline. If the last event is "Authentication Required: OTP" with no subsequent events, the customer never entered the OTP. The bank sent the OTP but the customer did not complete the step. The transaction will eventually time out and be marked as abandoned.
Building an Internal Support Tool
Instead of having support agents ask engineers to look up transaction details, build a simple internal tool that anyone on the support team can use.
// Simple Express endpoint for your internal admin panel
app.get('/admin/transaction/:reference', async (req, res) => {
const reference = req.params.reference;
// Fetch transaction details
const txResponse = await fetch(
'https://api.paystack.co/transaction/verify/' + reference,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const txData = await txResponse.json();
// Fetch timeline
const tlResponse = await fetch(
'https://api.paystack.co/transaction/timeline/' + reference,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const tlData = await tlResponse.json();
// Fetch order from our database
const order = await db.query(
'SELECT * FROM orders WHERE paystack_reference = $1',
[reference]
);
res.json({
transaction: {
reference: txData.data.reference,
amount: txData.data.amount / 100,
currency: txData.data.currency,
status: txData.data.status,
channel: txData.data.channel,
customer_email: txData.data.customer.email,
gateway_response: txData.data.gateway_response,
paid_at: txData.data.paid_at,
},
timeline: tlData.data.history.map(function(e) {
return {
time_seconds: e.time,
type: e.type,
message: e.message,
};
}),
order: order.rows[0] || null,
});
});
This endpoint combines three data sources into one view: the Paystack transaction details, the timeline events, and your internal order record. A support agent enters the reference, sees everything in one place, and can resolve the customer's issue without engineering help.
Add this to your admin dashboard with a search bar. The agent types a reference or customer email, and the tool displays the transaction history and timeline.
Using the Timeline to Debug Webhook Issues
Webhook problems are one of the most common integration issues. The timeline helps you pinpoint exactly what happened.
Scenario: Webhook was sent but not acknowledged
The timeline shows "Webhook sent to [your URL]" followed by "Webhook failed (timeout)" or "Webhook failed (500)." This means Paystack sent the webhook, but your server did not respond correctly. Check your server logs around the timestamp for errors.
Scenario: No webhook event in the timeline
If the transaction succeeded but the timeline does not show a webhook event, the webhook may not be configured correctly on your Paystack dashboard. Check Settings > API Keys & Webhooks in your Paystack dashboard to verify the webhook URL.
Scenario: Webhook acknowledged but order not fulfilled
The timeline shows "Webhook acknowledged (200 OK)" but the customer did not receive their product. This means Paystack delivered the webhook and your server returned 200, but your webhook handler did not fulfill the order. The bug is in your code, not in Paystack. Check your webhook handler logic: is it verifying the transaction? Is it checking the amount and status? Is it updating the order in the database?
For each webhook issue pattern, the timeline tells you whether the problem is on Paystack's side (webhook not sent), your network's side (webhook not received), or your application's side (webhook received but not processed correctly).
Storing Timeline Data for Post-Mortem Analysis
For high-volume applications, store timeline data for failed transactions automatically. This gives you a dataset for analyzing failure patterns without having to query Paystack on demand.
// In your webhook handler, store the timeline for failed transactions
async function onTransactionFailed(reference) {
try {
const response = await fetch(
'https://api.paystack.co/transaction/timeline/' + reference,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const data = await response.json();
await db.query(
'INSERT INTO transaction_timelines (reference, timeline_data, created_at) VALUES ($1, $2, $3)',
[reference, JSON.stringify(data.data.history), new Date()]
);
} catch (error) {
console.log('Failed to store timeline for ' + reference + ': ' + error.message);
}
}
Over time, this data reveals patterns. You might discover that 40% of your failed payments fail at the OTP step (customers not receiving or entering the OTP). That insight leads you to add clearer OTP instructions in your checkout flow. Or you might find that failures spike at certain times of day, pointing to bank-side processing issues.
For the full accept payments workflow, see the complete accept payments guide. For understanding bank decline reasons, see reading gateway responses.
Key Takeaways
- ✓The Transaction Timeline API endpoint is GET /transaction/timeline/{id_or_reference}. It returns an array of events in chronological order, each with a timestamp, action type, and message.
- ✓Use the timeline to answer "what happened?" questions from customers and support agents. Instead of guessing, you can see the exact sequence of events for any transaction.
- ✓Common timeline events include: transaction initialized, card details entered, authorization sent to bank, bank responded (approve/decline), 3DS triggered, OTP sent, charge completed, webhook delivered, and settlement processed.
- ✓The timeline shows failure points clearly. If a charge failed, the timeline tells you whether it failed at the bank (insufficient funds, card blocked), at 3DS (OTP timeout), or at your webhook (delivery failed).
- ✓Build an internal support tool that pulls the timeline for any transaction reference. Your support agents can look up a customer complaint in seconds instead of escalating to the engineering team.
- ✓The timeline is read-only and does not affect the transaction. You can query it as many times as needed without side effects.
Frequently Asked Questions
- Is the Transaction Timeline available for all Paystack transactions?
- Yes. The timeline is available for all transaction types: card, bank transfer, USSD, and mobile money. The specific events differ by channel, but every transaction has a timeline. You can query it by transaction ID or by reference.
- How long is timeline data available after a transaction?
- Paystack retains transaction timeline data for a significant period. You can query timelines for transactions that happened weeks or months ago. For long-term analysis, store the timeline data in your own database so you do not depend on Paystack retention.
- Can I use the timeline to detect fraud?
- Indirectly. The timeline shows patterns like rapid failed attempts (card testing), OTP failures (potential stolen card without access to the cardholder phone), and unusual time patterns. Combined with other signals (IP address, customer history), timeline data can feed into fraud detection logic.
- Does querying the timeline affect the transaction?
- No. The timeline endpoint is read-only. Querying it does not modify the transaction, trigger webhooks, or have any side effects. You can query it as many times as needed for debugging.
- Can support agents access the timeline directly on the Paystack dashboard?
- Yes. You can view transaction details including step-by-step events on the Paystack dashboard by clicking on a transaction. However, the API is more useful when you want to integrate timeline data into your own support tools, where agents can see both the Paystack events and your internal order data side by side.
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