Fraud Signals Available in Paystack Transaction Data
Key fraud signals in Paystack transaction data: (1) risk_action — Paystack's own risk score: "allow", "deny", or "review"; (2) ip_address — the customer's IP at charge time; (3) gateway_response — card network response messages like "Do Not Honor" or "Stolen Card"; (4) channel — card charges carry more fraud risk than bank transfers; (5) authorization.reusable — a card that suddenly becomes reusable without prior subscription setup is suspicious; (6) attempts — multiple failed attempts before success on the same reference indicate probing.
Key Fraud Signal Fields in Paystack Transaction Data
When you call GET /transaction/verify/:reference or receive a charge.success webhook, the transaction object contains several fraud-relevant fields:
| Field | Location | What It Signals |
|---|---|---|
risk_action |
Top-level | Paystack's internal risk score: "allow", "deny", or "review". "review" means proceed with caution. |
ip_address |
Top-level | Customer's IP at time of charge. Velocity-check this against recent failed attempts. |
gateway_response |
Top-level | Raw card network response: "Approved", "Do Not Honor", "Stolen Card", "Lost Card", "Restricted Card". |
channel |
Top-level | Payment method: "card", "bank", "ussd", "mobile_money". Card has highest fraud surface. |
authorization.bin |
Nested | First 6 digits of card. Cross-reference against known fraud BINs from your history. |
authorization.country_code |
Nested | Card-issuing country. Mismatch with customer's IP country is a weak fraud signal. |
authorization.reusable |
Nested | If true and the customer has no subscription, the card may have been saved without consent. |
Building a Fraud Score From Paystack Data
// fraud-score.js
var blockedIPs = new Set(); // Load from Redis or DB
function scoreFraud(transaction) {
var score = 0;
var flags = [];
// Hard signals from Paystack risk engine
if (transaction.risk_action === 'deny') {
score += 100;
flags.push('paystack_risk_deny');
}
if (transaction.risk_action === 'review') {
score += 20;
flags.push('paystack_risk_review');
}
// Gateway response signals
var badResponses = ['Do Not Honor', 'Stolen Card', 'Lost Card', 'Restricted Card', 'Pick Up Card'];
if (badResponses.some(r => transaction.gateway_response?.includes(r))) {
score += 25;
flags.push('bad_gateway_response:' + transaction.gateway_response);
}
// IP velocity signals
if (blockedIPs.has(transaction.ip_address)) {
score += 30;
flags.push('blocked_ip');
}
// Channel signals — card on large amount
if (transaction.channel === 'card' && transaction.amount > 5000000) { // NGN 50,000
score += 10;
flags.push('large_card_charge');
}
// Country mismatch
var cardCountry = transaction.authorization?.country_code;
var ipCountry = resolveIPCountry(transaction.ip_address); // your GeoIP lookup
if (cardCountry && ipCountry && cardCountry !== ipCountry) {
score += 15;
flags.push('country_mismatch:' + cardCountry + '_vs_' + ipCountry);
}
return { score, flags, action: score >= 50 ? 'hold' : score >= 20 ? 'log' : 'pass' };
}
// Use in webhook handler
if (event.event === 'charge.success') {
var fraud = scoreFraud(event.data);
if (fraud.action === 'hold') {
await db.orders.update({ id: orderId, status: 'fraud_hold', fraud_flags: fraud.flags });
await alertTeam('Fraud hold on order ' + orderId + ' score ' + fraud.score);
// Do NOT fulfill the order yet
} else {
await fulfillOrder(orderId);
}
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Learn More
This guide is part of the Paystack security guide.
Key Takeaways
- ✓Paystack includes a risk_action field on transaction data: "allow", "deny", or "review". Log this for every transaction.
- ✓The ip_address field identifies the customer's IP. Cross-reference it against blocklists and velocity checks.
- ✓gateway_response strings like "Do Not Honor", "Stolen Card", and "Lost Card" are hard fraud signals — escalate these immediately.
- ✓Card channel transactions carry higher fraud risk than bank transfers. Apply extra scrutiny to card charges for high-value orders.
- ✓A transaction with many prior failed attempts before succeeding suggests probing — flag these for manual review.
- ✓Build a simple fraud score by summing risk points: blocked IP (+30), bad gateway_response (+25), risk_action=review (+20), card channel on large amount (+10).
Frequently Asked Questions
- What does risk_action: "review" mean in practice?
- It means Paystack's fraud engine flagged the transaction as potentially suspicious but did not block it outright. You should log these transactions, consider adding a manual review step before fulfilling high-value orders, and monitor whether "review" transactions have a higher chargeback rate in your data.
- Can I access these fraud fields in the Paystack dashboard?
- Some fields like gateway_response and channel are visible in the transaction detail view. risk_action and ip_address are in the API response. For systematic fraud analysis, pull transaction data via the API and store these fields in your own database for querying.
- Should I block transactions with risk_action: "deny"?
- Paystack already blocks deny-rated transactions before they succeed — a charge.success webhook will not fire for them. If you see risk_action: "deny" in a failed transaction notification, record it. If you proactively check via the API, treat deny as a hard stop.
- What is the most reliable fraud signal in Paystack data?
- gateway_response values like "Stolen Card" and "Lost Card" are the most reliable — these come directly from the card-issuing bank and are almost never false positives. risk_action from Paystack's engine is also reliable. IP velocity (same IP, many different emails, rapid succession) is highly predictive for card testing attacks.
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