The Problem: Fraud Gets Past Signup, Hits Your Payment System
Here is the typical signup flow for a SaaS application:
- User enters email, password, and phone number
- SMS OTP is sent and verified
- Account is created
- User enters payment information (Stripe Checkout or Elements)
- Stripe creates a customer, charges the card, starts the subscription
The fraud happens at step 1. A bot or human fraudster enters a disposable email (or a real person's email obtained from a breach), a TextNow phone number, and completes the SMS verification. By step 3, they have a fully authenticated account. By step 4, they are interacting with Stripe.
Stripe has its own fraud detection (Stripe Radar) that operates at the payment level. Radar is excellent at identifying stolen credit cards and suspicious payment patterns. But Radar cannot see what happened during signup. It does not know the account was created with a VOIP number. It does not know the signup IP is a known VPN exit node. It does not know three other accounts were created from the same device fingerprint in the last hour.
Stripe Radar evaluates the payment. Auth1 evaluates the person. Fraud prevention requires both, but the identity layer has to come first. By the time a fraudulent payment reaches Radar, the damage is already happening: the account exists, the free trial is consumed, the API is being abused, and your metrics are polluted.
What Card Testing Looks Like
Card testing (also called card cracking or BIN attacks) is when a fraudster uses your payment form to validate stolen credit card numbers. The process works like this:
- Fraudster obtains a batch of stolen card numbers from the dark web ($1-10 per card)
- They create an account on your platform (using a VOIP number to pass phone verification)
- They attempt small charges ($0.50-1.00) through your Stripe integration to test if cards are live
- Cards that succeed are confirmed as valid and sold at a premium ($25-100 per card)
- You are left with the failed charges, disputes, and a degraded Stripe risk score
The economics are simple. If a fraudster tests 100 cards through your platform, even at a 10% success rate, they profit. You absorb the costs:
| Cost Item | Per Incident | 100 Card Test |
|---|---|---|
| Stripe processing fee (failed) | $0.30 | $30.00 |
| Chargeback fee (successful tests) | $15.00 | $150.00 |
| SMS verification cost | $0.0075 | $0.75 |
| Support time (investigating) | $5-15 | $50-150 |
| Stripe risk score degradation | Cumulative | Increased decline rates for real customers |
| Total | $230-330 |
A single card-testing session costs you $230-330. Persistent attackers can run multiple sessions per week. Over a month, card-testing attacks can cost $1,000-5,000 for a mid-size SaaS.
The Free Trial Problem
Free trial abuse is simpler but equally damaging. The pattern is:
- Service arbitrage: A user creates 10 accounts with 10 VOIP numbers to use your product for free indefinitely, rotating through 14-day trials
- API abuse: Accounts on free tiers are used to scrape your API, run automated workflows, or consume compute resources at no cost
- Competitor intelligence: Competitors create accounts to monitor your product, pricing changes, and feature releases without paying
- Content theft: For content platforms, free trial accounts bulk-download paywalled content
The financial impact depends on your product. For a SaaS charging $49/month, each trial-abusing account represents $49 in lost potential revenue. If 200 of your 2,000 monthly trials are abusers, that is $9,800/month in revenue that never converts.
The Architecture: Auth1 Sits Between Signup and Stripe
The solution is to insert a fraud-evaluation layer between your signup form and your payment system. Auth1 acts as a gatekeeper: only verified, low-risk users reach Stripe.
Integration Guide: Auth1 + Stripe
Here is the complete server-side integration for a Node.js/Express application using Auth1 for signup verification and Stripe for payments.
const express = require('express'); const Stripe = require('stripe'); const stripe = Stripe(process.env.STRIPE_SECRET_KEY); const AUTH1_KEY = process.env.AUTH1_API_KEY; const AUTH1_BASE = 'https://auth-api.z101.ai/api'; const app = express(); app.use(express.json()); // Step 1: Verify phone number (called when user submits signup form) app.post('/api/signup/verify', async (req, res) => { const { phone, email } = req.body; // Auth1 handles VOIP detection + OTP delivery in one call const auth1Res = await fetch(`${AUTH1_BASE}/auth/sms/request`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': AUTH1_KEY, }, body: JSON.stringify({ phone, email, voipPolicy: 'block', }), }); const data = await auth1Res.json(); if (data.isVoip || data.riskScore > 60) { return res.status(400).json({ error: 'verification_failed', message: 'Unable to verify this phone number. Please use a mobile number.', }); } // OTP sent to verified number res.json({ success: true, riskScore: data.riskScore }); }); // Step 2: Complete signup + create Stripe customer app.post('/api/signup/complete', async (req, res) => { const { phone, email, name, otp } = req.body; // Verify OTP with Auth1 const verifyRes = await fetch(`${AUTH1_BASE}/auth/sms/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': AUTH1_KEY, }, body: JSON.stringify({ phone, code: otp }), }); const verifyData = await verifyRes.json(); if (!verifyData.verified) { return res.status(400).json({ error: 'Invalid verification code' }); } // Create Stripe customer with fraud metadata const customer = await stripe.customers.create({ email, name, phone, metadata: { auth1_risk_score: String(verifyData.riskScore), auth1_carrier: verifyData.carrierInfo?.name || 'unknown', auth1_line_type: verifyData.carrierInfo?.lineType || 'unknown', auth1_verified: 'true', }, }); // Create your user in your database const user = await db.users.create({ email, name, phone, stripeCustomerId: customer.id, auth1RiskScore: verifyData.riskScore, }); // Create Stripe Checkout session for payment const session = await stripe.checkout.sessions.create({ customer: customer.id, mode: 'subscription', line_items: [{ price: 'price_xxx', quantity: 1 }], success_url: 'https://app.example.com/welcome', cancel_url: 'https://example.com/pricing', }); res.json({ checkoutUrl: session.url }); });
What Happens at Each Step
Before Auth1 (No Protection)
- Any phone number passes verification (including Google Voice, TextNow, TextFree)
- SMS OTP is sent to every number ($0.0075/SMS wasted on VOIP)
- Fake accounts are created and reach Stripe
- Card-testing attacks run through your Stripe integration
- You discover the problem 2-4 weeks later when chargebacks arrive
After Auth1 (Protected)
- VOIP numbers are blocked before OTP is sent (zero SMS cost on fraud)
- High-risk numbers (recently ported, suspicious carrier) are flagged or blocked
- Device fingerprinting catches repeat offenders across phone numbers
- Only verified, low-risk users create Stripe customers
- Risk metadata is attached to Stripe customers for Radar to use
When you pass Auth1's risk score as Stripe customer metadata, Stripe Radar can use it in custom rules. For example, you can create a Radar rule that blocks payments from customers with auth1_risk_score > 50. This gives Radar additional signal it does not have on its own, making it significantly more effective.
Advanced Patterns
Pattern 1: Tiered Access Based on Risk Score
Instead of binary allow/block, use the risk score to gate access levels:
- Risk 0-25: Full free trial, immediate payment access
- Risk 26-50: Limited free trial (3 days instead of 14), requires payment upfront
- Risk 51-70: No free trial, payment required immediately, $1.00 verification charge
- Risk 71-100: Signup blocked
Pattern 2: Webhook-Based Monitoring
Auth1 sends webhooks for every verification event. Combine these with Stripe webhooks to build a real-time fraud dashboard:
// Auth1 webhook: verification completed app.post('/webhooks/auth1', (req, res) => { const { event, data } = req.body; if (event === 'verification.completed') { // Log for analytics analytics.track('signup_verified', { phone: data.phone, riskScore: data.riskScore, isVoip: data.isVoip, carrier: data.carrierInfo.name, }); } if (event === 'verification.blocked') { // Alert on blocked attempts slack.notify(`Blocked signup: ${data.phone} (${data.carrierInfo.name}, score: ${data.riskScore})`); } res.sendStatus(200); }); // Stripe webhook: dispute created app.post('/webhooks/stripe', (req, res) => { const event = req.body; if (event.type === 'charge.dispute.created') { const customer = event.data.object.customer; // Cross-reference with Auth1 risk score // Automatically ban accounts with disputes + high risk } res.sendStatus(200); });
Pattern 3: Pre-Stripe Verification Charge
For high-risk but not blocked signups (risk score 50-70), use Stripe's Setup Intents to verify the payment method before granting access:
- Create a Stripe SetupIntent with
usage: 'off_session' - Charge $0.50 as a verification hold (refunded immediately)
- This confirms the card is real and belongs to the person signing up
- Combined with Auth1's phone verification, this creates a two-factor fraud gate
Results: Before and After
Companies that implement Auth1 as a pre-Stripe verification layer typically see these changes within 30 days:
| Metric | Before Auth1 | After Auth1 | Change |
|---|---|---|---|
| Fake account rate | 15-25% | 2-4% | -85% |
| Card-testing incidents/month | 3-8 | 0-1 | -90% |
| Chargebacks/month | $300-1,200 | $0-75 | -92% |
| Wasted SMS costs/month | $15-45 | $0-3 | -95% |
| Trial-to-paid conversion | 8-12% | 14-22% | +75% |
| Stripe dispute rate | 0.8-1.5% | 0.1-0.3% | -80% |
The trial-to-paid conversion increase is particularly significant. When you remove fake accounts from your funnel, your conversion metrics reflect actual user behavior, and they are almost always better than the fraud-polluted numbers suggest.
Auth1 integrates with Stripe in under an hour. Sign up at auth1.ai/signup, get your API key, and add the verification call before your Stripe customer creation. The free tier includes 1,000 verifications/month, enough to test the integration and measure the impact on your signup quality.