Chargeback Prevention

Passing Customer Metadata to Stripe

How to include email, IP, and enhanced metadata in Stripe payments for better chargeback risk scoring.

6 min read Last updated March 14, 2026

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.