Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to check fraud risk in Stripe payments

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.

What you'll learn

  • How to access Radar risk scores and risk levels on charges
  • How to interpret risk signals like CVC and ZIP checks
  • How to set up payment reviews with Radar for Fraud Teams
  • How to build a fraud monitoring API for your dashboard
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner6 min read10 minutesStripe API v2024-12+, Node.js 18+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2
3async function getRiskAssessment(chargeId) {
4 const charge = await stripe.charges.retrieve(chargeId);
5 const outcome = charge.outcome;
6
7 console.log('Risk score:', outcome.risk_score); // 0-100
8 console.log('Risk level:', outcome.risk_level); // normal, elevated, highest
9 console.log('Type:', outcome.type); // authorized, blocked, manual_review
10 console.log('Reason:', outcome.reason); // rule match or null
11 console.log('Seller message:', outcome.seller_message);
12
13 return outcome;
14}
15
16getRiskAssessment('ch_chargeId');

Expected result: Console displays the risk score (0-100), risk level, and outcome type for the charge.

2

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.

typescript
1async function getVerificationDetails(chargeId) {
2 const charge = await stripe.charges.retrieve(chargeId);
3 const checks = charge.payment_method_details?.card?.checks;
4
5 if (checks) {
6 console.log('CVC check:', checks.cvc_check); // pass, fail, unavailable, unchecked
7 console.log('Address line 1:', checks.address_line1_check);
8 console.log('ZIP check:', checks.address_postal_code_check);
9 }
10
11 // Flag suspicious combinations
12 const suspicious =
13 checks?.cvc_check === 'fail' ||
14 (checks?.address_postal_code_check === 'fail' && charge.amount > 10000);
15
16 if (suspicious) {
17 console.log('WARNING: Suspicious verification results');
18 }
19
20 return checks;
21}
22
23getVerificationDetails('ch_chargeId');

Expected result: Console shows individual verification check results and flags suspicious combinations.

3

Build a fraud monitoring endpoint

Create an API endpoint that returns risk information for recent payments, useful for building internal fraud dashboards.

typescript
1const express = require('express');
2const app = express();
3app.use(express.json());
4
5app.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 hours
11 },
12 });
13
14 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 }));
28
29 // Sort by risk score descending
30 riskReport.sort((a, b) => (b.riskScore || 0) - (a.riskScore || 0));
31
32 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});
42
43app.listen(3000);

Expected result: API returns a sorted list of recent charges with risk scores, helping you identify high-risk transactions quickly.

4

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.

typescript
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_SECRET
9 );
10 } catch (err) {
11 return res.status(400).send(`Webhook Error: ${err.message}`);
12 }
13
14 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 team
23 }
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 charge
31 break;
32 }
33 case 'review.opened': {
34 console.log('Manual review opened:', event.data.object.charge);
35 break;
36 }
37 }
38
39 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

fraud-monitoring.js
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2const express = require('express');
3const app = express();
4
5app.use(express.json());
6
7// Get risk details for a specific charge
8app.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});
26
27// Get summary of recent high-risk charges
28app.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});
47
48// Webhook for fraud alerts
49app.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_SECRET
55 );
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});
70
71app.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.

ChatGPT Prompt

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.

Stripe Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.