Fidro reads customer metadata from your Stripe PaymentIntent to improve chargeback risk scoring. Without metadata, Fidro analyses card, billing, AVS/CVC, and Stripe Radar data automatically. With metadata, you unlock email validation, IP intelligence, geo-mismatch detection, and behavioural checks on top.
Metadata fields
Add these to your PaymentIntent's metadata object:
Core fields (recommended)
| Key | Value | Purpose |
|---|---|---|
customer_email |
Customer's email address | Disposable email detection, blocklist matching |
customer_ip |
Customer's IP address | VPN/proxy/Tor detection, geo-mismatch |
Enhanced fields (optional)
These unlock additional fraud checks. All are optional — pass whichever ones you have available.
| Key | Value | Purpose |
|---|---|---|
customer_account_age_minutes |
Minutes since account was created | Flags very new accounts (< 30 min) making purchases |
customer_order_count |
Number of previous orders | Distinguishes first-time buyers from returning customers |
customer_phone |
Customer's phone number | Absence of phone is a minor risk signal |
device_fingerprint_id |
Unique device/browser identifier | Detects same device across multiple accounts |
session_duration_seconds |
Seconds spent on checkout page | Flags unusually fast checkouts (< 30s, likely bots) |
checkout_referrer |
HTTP referrer of the checkout page | Stored for analysis |
Where to add metadata
Metadata must be set when you create the PaymentIntent (or before it's confirmed). You can't add metadata after the payment succeeds.
Stripe Checkout Sessions
const session = await stripe.checkout.sessions.create({
line_items: [{ price: 'price_xxx', quantity: 1 }],
mode: 'payment',
payment_intent_data: {
metadata: {
customer_email: customerEmail,
customer_ip: customerIp,
// Enhanced (optional)
customer_account_age_minutes: String(accountAgeMinutes),
customer_order_count: String(user.orderCount),
customer_phone: user.phone || '',
device_fingerprint_id: deviceId,
session_duration_seconds: String(sessionDuration),
},
},
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
});
Stripe Elements / Custom Payment Flow
const paymentIntent = await stripe.paymentIntents.create({
amount: 4999,
currency: 'usd',
metadata: {
customer_email: 'jane@example.com',
customer_ip: '203.0.113.45',
// Enhanced (optional)
customer_account_age_minutes: String(
Math.floor((Date.now() - user.createdAt) / 60000)
),
customer_order_count: String(user.orderCount),
customer_phone: user.phone || '',
device_fingerprint_id: req.body.deviceId,
session_duration_seconds: String(req.body.sessionDuration),
},
});
Stripe Subscriptions
For recurring payments, set metadata on the subscription. Stripe copies subscription metadata to each invoice's PaymentIntent:
const subscription = await stripe.subscriptions.create({
customer: 'cus_xxx',
items: [{ price: 'price_xxx' }],
payment_settings: {
payment_method_options: {
card: { request_three_d_secure: 'automatic' },
},
},
metadata: {
customer_email: 'jane@example.com',
customer_ip: '203.0.113.45',
customer_account_age_minutes: String(accountAgeMinutes),
customer_order_count: String(user.orderCount),
},
});
Note: For subscriptions, the IP and session data may change over time. Consider updating the subscription metadata when the customer logs in or updates their payment method.
Laravel Cashier
// One-time charge
$user->charge(4999, $paymentMethod, [
'metadata' => [
'customer_email' => $user->email,
'customer_ip' => request()->ip(),
// Enhanced (optional)
'customer_account_age_minutes' => (string) $user->created_at->diffInMinutes(now()),
'customer_order_count' => (string) $user->orders()->count(),
'customer_phone' => $user->phone ?? '',
'device_fingerprint_id' => request()->input('device_id', ''),
'session_duration_seconds' => request()->input('session_duration', ''),
],
]);
// Subscription
$user->newSubscription('default', 'price_xxx')
->withMetadata([
'customer_email' => $user->email,
'customer_ip' => request()->ip(),
'customer_account_age_minutes' => (string) $user->created_at->diffInMinutes(now()),
'customer_order_count' => (string) $user->orders()->count(),
])
->create($paymentMethod);
What Fidro analyses automatically (no metadata needed)
Even without any metadata, Fidro extracts and analyses these signals directly from the Stripe charge object:
- Card funding type — Prepaid/virtual cards flagged (2–4x higher chargeback rate)
- AVS results — Address and postal code verification from the card network
- CVC check — Card security code validation result
- Stripe Radar risk level — Stripe's own fraud assessment (normal/elevated/highest)
- Card country — Issuing country of the card
- Billing country — Country from billing address
- Amount and currency — High-value transaction detection
- Transaction velocity — Repeat charge patterns (requires email or IP metadata)
Adding email and IP metadata typically improves risk score accuracy by 30–40%, because it enables the full suite of email and network checks. Adding enhanced metadata (account age, order count, device fingerprint) can improve accuracy by an additional 15–20%.
Verifying metadata is being sent
After a test payment, check the Transactions page in your Fidro dashboard. The transaction detail view shows:
- Customer Email — should show the email you passed
- Customer IP — should show the IP you passed
- Account Age — should show minutes since account creation
- Device ID — should show the fingerprint you passed
If these show "—", the metadata isn't being included in the PaymentIntent.
Important: Stripe metadata values must be strings. Convert numbers with
String()(JavaScript) or(string)(PHP) before passing them.