Skip to main content
A chargeback is the opposite problem from a real-time fraud check. The sale already happened, the goods already shipped, and weeks later the cardholder tells their bank the charge was unauthorized. When that “friendly fraud” claim lands, the burden flips to you: prove the purchase was the genuine account holder. This recipe builds that proof from the buyer’s device history so your dispute team has a defensible evidence package to attach to the representment. This is the after-the-sale play. For scoring the payment as it happens and gating the charge in the moment, the checkout recipe is the real-time half and is not repeated here. ShieldLabs surfaces the evidence. Your dispute team owns the response and the bank decides the outcome. ShieldLabs does not win or lose the dispute, and it does not prove identity. What it gives you is device continuity: a record that the same device, from the same country, with clean Risk Scores, made both the disputed order and earlier purchases that were never contested.

The pattern

1

Stamp every order with its identity

At each purchase, persist the visit’s VisitorID, DeviceID, and UserHID next to the order record. This is the snapshot of who bought, captured at buy time, that you replay later.
2

Wait for the dispute

Nothing happens until a chargeback arrives. The evidence sits in your own database, costing nothing, until you need it.
3

Reconstruct the buyer's history

When a dispute lands, pull that buyer’s prior visits from the History API by user_hid or device_id, newest first, to show the disputed order and the undisputed ones came from the same device.
4

Export and attach

Assemble the matching rows into a CSV or JSON evidence package and attach it to your dispute response. Your team writes the representment; ShieldLabs only supplies the device record.

Step 1: store the identity at purchase

The real-time score belongs to the checkout recipe. Here the only extra work is durable: when the order is confirmed, save the three identifiers from that visit’s scored result alongside the order. The shared webhook-cache helper (waitForScore plus scoreCache) already hands you these fields, so this recipe shows only what to persist.
order-stamp.js
// Inside your order-confirmation handler, after the charge succeeds.
// `shield` is the scored result the shared waitForScore helper returns.
async function recordOrder(order, shield) {
  await db.orders.update(order.id, {
    // The identity stamp you will replay if this charge is ever disputed.
    shield_visitor_id: shield.visitorID, // VisitorID for this visit
    shield_device_id:  shield.deviceID,  // server-derived, durable, browser-bound
    shield_user_hid:   shield.userHID,   // the hashed account id you passed in
    shield_score:      shield.score,     // 0 to 100 at buy time
    shield_country:    shield.country,   // IP country at buy time
    shield_request_id: shield.requestID, // ties back to the exact identification
  });
}
The DeviceID is server-derived, durable, and bound to the browser that made the visit. It is the field that gives the evidence its weight: a returning buyer on the same browser keeps the same DeviceID across sessions, which is exactly the continuity a dispute response wants to demonstrate.

Step 2: reconstruct the buyer on dispute

When the chargeback notification arrives, look up the disputed order, then query the History API for that buyer’s other visits. Search by user_hid to gather every visit tied to the account, or by device_id to gather every visit from that exact device. Both come back as an array of snapshots, newest first.
dispute-evidence.js
// `dispute` carries the order id your processor (Stripe, Adyen, etc.) reported.
async function buildEvidence(dispute) {
  const order = await db.orders.get(dispute.orderId);

  const domain = 'myshop.com';
  const secret = process.env.SHIELDLABS_SECRET;

  // Pull this account's visit history, newest first. Search by user_hid to
  // capture the account, or by device_id to capture the exact device.
  // Each returned row bills 1 request; an empty result still bills 1.
  const res = await fetch(
    `https://api.shieldlabs.ai/${domain}:${secret}` +
    `/history/user_hid/${order.shield_user_hid}?limit=50`
  );
  const snapshots = await res.json(); // [] when nothing matches

  // Keep the visits that share the disputed order's device. Same DeviceID
  // across purchases is the continuity claim your representment makes.
  const sameDevice = snapshots.filter(
    (s) => s.DeviceID === order.shield_device_id
  );

  return sameDevice.map((s) => ({
    when:     s.LastRequestTime,
    device:   s.DeviceID,
    visitor:  s.VisitorID,
    country:  s.Country,
    score:    s.Score,    // a clean score here strengthens the case
    browser:  s.Browser,
    device_type: s.DeviceType,
  }));
}
Each snapshot carries the Score and its Details (an array of { Value, Description }), the DeviceID, VisitorID, UserHID, IP, Country, Browser, DeviceType, and LastRequestTime. Read the Score and each detail’s numeric Value, never the human-readable Description label, which can change. A run of clean or low scores from one device, across the disputed order and earlier undisputed ones, is the pattern that makes the package persuasive.
The History API bills 1 request per returned row, and a lookup that matches nothing still bills 1 request. A device_id or user_hid search returning 50 snapshots costs 50 requests. Set limit to the smallest value that covers the order history you need to cite. Webhooks and dashboard exports stay free; the Billing page has the full breakdown.

Step 3: export the evidence package

You do not have to script the export at all. The dashboard Data tab lets you search by user_hid, visitor_id, or device_id, filter by date and score, and export the matching rows to CSV or JSON for free. That export is the attachment your dispute team hands to the processor. For an automated pipeline, serialize the rows from Step 2 into the same CSV or JSON your representment workflow expects. A package that holds up tends to show, side by side:
Evidence in the exportWhat it argues
Same DeviceID on the disputed order and prior ordersThe same physical device made the purchases, not a stranger.
Consistent Country across those visitsNo sudden geography change that would suggest a stolen card.
Clean or low Score on each visitNone of these purchases carried strong anonymity signals.
Earlier orders that were never disputedA pattern of legitimate use from this device.
LastRequestTime spanning weeks or monthsA relationship, not a one-off hit-and-run.

What this evidence is, and is not

Device continuity is supplementary evidence. It strengthens a representment; it does not stand alone, and it is honest about its limits.
  • It is not proof of identity. A DeviceID ties activity to a browser on a device, not to a named person. The same household member or a borrowed laptop produces the same DeviceID.
  • A different browser or device is a different DeviceID. A genuine buyer who switched laptops between purchases will not show device continuity, and its absence is not proof of fraud.
  • ShieldLabs does not decide the dispute. It supplies the device record. Your team writes the representment and the cardholder’s bank rules on it.
Pair the device history with the rest of your case (the AVS and CVV result, delivery confirmation, login history, prior order fulfillment) so the package argues from several angles, not one.

Next

Checkout protection

The real-time half: score the payment before the charge and gate it on the band.

Account takeover

Catch the new-device login that often precedes the fraudulent purchase in the first place.