Every Stripe payment receives a Radar risk score from 0-100 and a risk level (normal, elevated, highest). You can access this data on every charge object to build fraud monitoring, flag high-risk transactions for review, and track fraud patterns. This guide covers reading risk scores, setting up review queues, and building a fraud monitoring endpoint.
Understanding and Monitoring Fraud Risk in Stripe Payments
Stripe Radar assigns a risk score (0 to 100) and a risk level (normal, elevated, highest) to every payment. These are available on the charge object's outcome field. Higher scores indicate greater fraud risk. You can use these scores to flag transactions in your internal systems, create custom alerts, and feed data into your own fraud review processes.
Prerequisites
- A Stripe account with completed payments (test or live)
- Node.js 18 or later installed
- Stripe Node.js SDK: npm install stripe
Step-by-step guide
Read the risk score on a charge
Read the risk score on a charge
Every successful or failed charge includes an outcome object with the Radar risk assessment. Access it via the API.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23async function getRiskAssessment(chargeId) {4 const charge = await stripe.charges.retrieve(chargeId);5 const outcome = charge.outcome;67 console.log('Risk score:', outcome.risk_score); // 0-1008 console.log('Risk level:', outcome.risk_level); // normal, elevated, highest9 console.log('Type:', outcome.type); // authorized, blocked, manual_review10 console.log('Reason:', outcome.reason); // rule match or null11 console.log('Seller message:', outcome.seller_message);1213 return outcome;14}1516getRiskAssessment('ch_chargeId');Expected result: Console displays the risk score (0-100), risk level, and outcome type for the charge.
Check verification signals
Check verification signals
Beyond the overall risk score, Stripe provides individual verification checks for CVC, address, and ZIP code. These signals help identify suspicious transactions.
1async function getVerificationDetails(chargeId) {2 const charge = await stripe.charges.retrieve(chargeId);3 const checks = charge.payment_method_details?.card?.checks;45 if (checks) {6 console.log('CVC check:', checks.cvc_check); // pass, fail, unavailable, unchecked7 console.log('Address line 1:', checks.address_line1_check);8 console.log('ZIP check:', checks.address_postal_code_check);9 }1011 // Flag suspicious combinations12 const suspicious =13 checks?.cvc_check === 'fail' ||14 (checks?.address_postal_code_check === 'fail' && charge.amount > 10000);1516 if (suspicious) {17 console.log('WARNING: Suspicious verification results');18 }1920 return checks;21}2223getVerificationDetails('ch_chargeId');Expected result: Console shows individual verification check results and flags suspicious combinations.
Build a fraud monitoring endpoint
Build a fraud monitoring endpoint
Create an API endpoint that returns risk information for recent payments, useful for building internal fraud dashboards.
1const express = require('express');2const app = express();3app.use(express.json());45app.get('/api/fraud/recent', async (req, res) => {6 try {7 const charges = await stripe.charges.list({8 limit: 50,9 created: {10 gte: Math.floor(Date.now() / 1000) - 86400, // Last 24 hours11 },12 });1314 const riskReport = charges.data.map(charge => ({15 id: charge.id,16 amount: charge.amount,17 currency: charge.currency,18 status: charge.status,19 riskScore: charge.outcome?.risk_score,20 riskLevel: charge.outcome?.risk_level,21 outcomeType: charge.outcome?.type,22 cvcCheck: charge.payment_method_details?.card?.checks?.cvc_check,23 zipCheck: charge.payment_method_details?.card?.checks?.address_postal_code_check,24 email: charge.billing_details?.email,25 country: charge.payment_method_details?.card?.country,26 created: new Date(charge.created * 1000).toISOString(),27 }));2829 // Sort by risk score descending30 riskReport.sort((a, b) => (b.riskScore || 0) - (a.riskScore || 0));3132 res.json({33 total: riskReport.length,34 highRisk: riskReport.filter(r => r.riskLevel === 'highest').length,35 elevated: riskReport.filter(r => r.riskLevel === 'elevated').length,36 charges: riskReport,37 });38 } catch (err) {39 res.status(400).json({ error: err.message });40 }41});4243app.listen(3000);Expected result: API returns a sorted list of recent charges with risk scores, helping you identify high-risk transactions quickly.
Set up webhook alerts for high-risk payments
Set up webhook alerts for high-risk payments
Use webhooks to get real-time alerts when Radar flags a payment as high-risk or sends it to manual review.
1app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {2 const sig = req.headers['stripe-signature'];3 let event;4 try {5 event = stripe.webhooks.constructEvent(6 req.body,7 sig,8 process.env.STRIPE_WEBHOOK_SECRET9 );10 } catch (err) {11 return res.status(400).send(`Webhook Error: ${err.message}`);12 }1314 switch (event.type) {15 case 'charge.succeeded': {16 const charge = event.data.object;17 if (charge.outcome?.risk_level === 'elevated' ||18 charge.outcome?.risk_level === 'highest') {19 console.log(`HIGH RISK PAYMENT: ${charge.id}`);20 console.log(`Score: ${charge.outcome.risk_score}`);21 console.log(`Amount: ${charge.amount} ${charge.currency}`);22 // Send Slack/email alert to your fraud team23 }24 break;25 }26 case 'radar.early_fraud_warning.created': {27 const warning = event.data.object;28 console.log(`EARLY FRAUD WARNING: charge ${warning.charge}`);29 console.log(`Fraud type: ${warning.fraud_type}`);30 // Immediately review or refund the charge31 break;32 }33 case 'review.opened': {34 console.log('Manual review opened:', event.data.object.charge);35 break;36 }37 }3839 res.json({ received: true });40});Expected result: Webhook handler sends alerts for high-risk payments and early fraud warnings in real time.
Complete working example
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);2const express = require('express');3const app = express();45app.use(express.json());67// Get risk details for a specific charge8app.get('/api/fraud/charge/:id', async (req, res) => {9 try {10 const charge = await stripe.charges.retrieve(req.params.id);11 res.json({12 id: charge.id,13 amount: charge.amount,14 riskScore: charge.outcome?.risk_score,15 riskLevel: charge.outcome?.risk_level,16 outcomeType: charge.outcome?.type,17 sellerMessage: charge.outcome?.seller_message,18 checks: charge.payment_method_details?.card?.checks,19 cardCountry: charge.payment_method_details?.card?.country,20 email: charge.billing_details?.email,21 });22 } catch (err) {23 res.status(400).json({ error: err.message });24 }25});2627// Get summary of recent high-risk charges28app.get('/api/fraud/summary', async (req, res) => {29 try {30 const hours = parseInt(req.query.hours) || 24;31 const charges = await stripe.charges.list({32 limit: 100,33 created: { gte: Math.floor(Date.now() / 1000) - (hours * 3600) },34 });35 const stats = { total: 0, normal: 0, elevated: 0, highest: 0, blocked: 0 };36 charges.data.forEach(c => {37 stats.total++;38 const level = c.outcome?.risk_level || 'normal';39 stats[level] = (stats[level] || 0) + 1;40 if (c.outcome?.type === 'blocked') stats.blocked++;41 });42 res.json(stats);43 } catch (err) {44 res.status(400).json({ error: err.message });45 }46});4748// Webhook for fraud alerts49app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {50 const sig = req.headers['stripe-signature'];51 let event;52 try {53 event = stripe.webhooks.constructEvent(54 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET55 );56 } catch (err) {57 return res.status(400).send(`Webhook Error: ${err.message}`);58 }59 if (event.type === 'radar.early_fraud_warning.created') {60 console.log('FRAUD WARNING:', event.data.object.charge);61 }62 if (event.type === 'charge.succeeded') {63 const c = event.data.object;64 if (c.outcome?.risk_score >= 65) {65 console.log(`High risk: ${c.id} score=${c.outcome.risk_score}`);66 }67 }68 res.json({ received: true });69});7071app.listen(3000, () => console.log('Fraud monitoring on port 3000'));Common mistakes when checkking fraud risk in Stripe payments
Why it's a problem: Not checking risk scores on authorized payments
How to avoid: A payment can be authorized but still have a high risk score. Monitor elevated-risk payments even when they succeed.
Why it's a problem: Ignoring early fraud warnings
How to avoid: Early fraud warnings from card networks strongly indicate fraud. Proactively refund these charges to avoid chargebacks.
Why it's a problem: Only monitoring blocked charges and missing elevated-risk ones
How to avoid: Payments with risk_level 'elevated' pass through but may still be fraudulent. Build alerts for scores above a threshold (e.g., 65+).
Why it's a problem: Not passing billing details which reduces Radar accuracy
How to avoid: Include email, name, and address on PaymentIntents so Radar has more signals for risk assessment.
Best practices
- Monitor risk scores on all payments, not just blocked ones
- Set up webhook alerts for charges with risk_score above 65
- Always act on radar.early_fraud_warning.created events — refund proactively
- Build a fraud dashboard that shows risk distribution over time
- Pass complete billing_details on every payment for better Radar accuracy
- Review elevated-risk payments weekly to identify patterns and create block rules
- Track your dispute rate and aim to keep it below 0.75%
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
How do I check fraud risk scores on Stripe payments? I want to access the Radar risk assessment for each charge, build alerts for high-risk transactions, and create a fraud monitoring dashboard. Show me Node.js code.
Help me build a fraud monitoring system for my Stripe integration. I need an API that returns risk scores for recent charges, webhook handlers for fraud warnings, and a summary endpoint for my internal dashboard. Use Node.js and Express.
Frequently asked questions
What does a Radar risk score of 65 mean?
Scores range from 0 (lowest risk) to 100 (highest risk). A score of 65 is considered elevated risk. Stripe classifies payments as normal (0-20), elevated (20-75), or highest (75-100), though exact thresholds vary.
Can I access risk scores in test mode?
Yes. Test charges include simulated risk scores. Use Stripe's test cards to get different risk levels, though the scores in test mode are not based on real fraud signals.
What is an early fraud warning?
Card networks (Visa, Mastercard) send early fraud warnings when a cardholder reports unauthorized activity. These arrive within 24-48 hours and precede formal chargebacks. Refunding proactively avoids the chargeback fee.
Does Radar cost extra?
Basic Radar is free with every Stripe account. Radar for Fraud Teams costs $0.07 per screened transaction and adds manual review queues, custom rules with more attributes, and block lists.
How do I reduce false positives in fraud blocking?
Start with review rules (manual_review) instead of block rules. Monitor results for a few weeks before promoting to automatic blocks. Use multiple signals (email + card + amount) instead of single attributes.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation