> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shieldlabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Chargeback Fraud and Disputes

> Learn how to detect chargeback fraud and build dispute evidence: tie each order to a durable device history for friendly-fraud representment.

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 tutorial builds that proof from the buyer's device history so your dispute team has a defensible evidence package.

## What is chargeback fraud and disputes?

A chargeback dispute is a cardholder asking their issuing bank to reverse a settled charge; chargeback fraud (often "friendly fraud") is when the cardholder made the purchase themselves, then disputes it anyway to keep the goods and recover the money. Winning a representment means showing the bank that the disputed order came from the genuine buyer, not a stranger with a stolen card.

## How ShieldLabs surfaces it

ShieldLabs stamps each order with a durable `DeviceID` and a [Risk Score (0–100)](/features/risk-scoring) at buy time, so a dispute can be answered with a record of the same device making the disputed order and earlier undisputed ones. Cookies and IP alone do not survive a real buyer's habits — a cleared cookie mints a new `VisitorID`, and a phone on cellular versus home Wi-Fi changes the IP and country between purchases. The `DeviceID` is server-derived, so it holds across cleared cookies, incognito, and IP rotation, which is exactly the continuity a dispute response needs.

ShieldLabs supplies the device record; your dispute team writes the representment and the cardholder's bank decides the outcome. Device continuity is supplementary evidence that strengthens a case, not proof of identity that settles it.

This is the after-the-sale play. For scoring the payment as it happens and gating the charge in the moment, see the [checkout tutorial](/use-case/payment-fraud).

## Build the chargeback evidence

Read the `UserHID`, the durable `DeviceID`, the IP `country`, and the stored Risk Score you stamped on every order. The rule your code applies: when a dispute lands, pull that buyer's prior visits by `user_hid` or `device_id`, then keep the rows where the same account and the same device, from a consistent country with clean or low scores, made both the disputed order and earlier undisputed ones. The outcome is a CSV package your dispute team attaches to the representment that shows a relationship, not a one-off stranger with a stolen card.

## Build it

<Steps>
  <Step title="Stamp every order with its identity at purchase">
    The real-time score belongs to the [checkout tutorial](/use-case/payment-fraud). Here the only extra work is durable: when the order is confirmed, save the identifiers from that visit's scored result alongside the order. The shared [`waitForScore` helper](/use-case) hands you `deviceID`, `userHID`, `score`, and `country`; pair them with the `request_id` you already hold so each order stamp ties back to one identification.

    ```js order-stamp.js theme={null}
    // Inside your order-confirmation handler, after the charge succeeds.
    // `shield` is the scored result the shared waitForScore helper returns;
    // `requestId` is the request_id you already passed into waitForScore.
    async function recordOrder(order, requestId, shield) {
      await db.orders.update(order.id, {
        // The identity stamp you will replay if this charge is ever disputed.
        shield_device_id:  shield.device_id,            // server-derived, durable, browser-bound
        shield_user_hid:   shield.user_hid,             // the hashed account id you passed in
        shield_score:      shield.score,                // 0 to 100 at buy time
        shield_country:    shield.public_ip?.country,  // IP country at buy time
        shield_request_id: requestId,        // ties back to the exact identification
      });
    }
    ```

    Nothing else happens until a chargeback arrives. The evidence sits in your own store, costing nothing, until you need it.
  </Step>

  <Step title="Reconstruct the buyer when a dispute lands">
    When the chargeback notification arrives, look up the disputed order, then query the [History API](/api/server-api) for that buyer's other visits. Lead with `user_hid`: it is your own hashed account id, so it survives both cleared cookies and a browser switch, and it gathers every visit tied to the account. Then mark which visits also share the disputed order's `device_id` as a stronger corroborating layer. The helper returns the `data` array, newest first.

    ```js dispute-evidence.js theme={null}
    const ALL_ZERO = '00000000-0000-0000-0000-000000000000';

    // `dispute` carries the order id your processor (Stripe, Adyen, etc.) reported.
    async function buildEvidence(dispute) {
      const order = await db.orders.get(dispute.orderId);

      // Lead with the account: user_hid survives cookie clears and browser
      // switches, so it captures the buyer's full history.
      const rows = await shieldHistory('user_hid', order.shield_user_hid, 50);

      return rows.map((s) => ({
        when:        s.created_at,
        device:      s.device_id,
        visitor:     s.visitor_id,
        country:     s.country,
        score:       s.score,    // a clean score here strengthens the case
        browser:     s.browser,
        device_type: s.device_type,
        // Same physical browser environment as the disputed order. The all-zero
        // DeviceID means the visit could not be identified, so it never counts.
        same_device:
          s.device_id === order.shield_device_id && s.device_id !== ALL_ZERO,
      }));
    }
    ```

    Each snapshot carries `score` and `score_details` (parse the JSON string for signal weights), plus `device_id`, `visitor_id`, `user_hid`, `ip`, `country`, `browser`, `device_type`, and `created_at`. Read `score` and each detail's numeric `Value`, never the human-readable `Description` label. 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.

    <Note>
      Exclude the all-zero `device_id` (`00000000-0000-0000-0000-000000000000`) before you lean on device continuity. A snippet-blocked or JavaScript-disabled visit cannot be identified, so the server returns that placeholder and scores it **90 or higher** — many unrelated visits share that value, so matching on it would bundle strangers into the "same device" set. The `same_device` filter above already drops it. If the disputed order itself carries the all-zero `device_id`, fall back to `user_hid` continuity.
    </Note>

    For a chargeback the signals work in reverse from a real-time check: the strongest evidence is the **absence** of anonymity signals. A buyer whose purchases scored Clean or Low, with no [VPN, Proxy, Tor, Privacy Relay, Datacenter IP, Abuser Flag, Anti-detect Browser, or OS / Timezone Mismatch](/features/anonymity-signals) firing, looks like an ordinary person on their own device. If they had hidden behind a VPN, proxy, or Tor, those signals would have fired and the real network IP (`local_ip`) would have disagreed with the public `public_ip` (`detection_flags.ip_mismatch`). The `UserHID` ties the orders to one account, the durable `DeviceID` ties them to one browser environment, and a steady `country` shows no sudden geography change.
  </Step>

  <Step title="Export and attach the package">
    You do not have to script the export. The dashboard [Data tab](/using-the-dashboard) lets you search by `user_hid`, `visitor_id`, or `device_id`, filter by date and score, and export the matching rows to CSV for free — that export is the attachment your dispute team hands to the processor. For an automated pipeline, serialize the rows from the previous step into the same format your representment workflow expects.

    A package that holds up tends to show, side by side:

    | Evidence in the export                                 | What it argues                                                             |
    | ------------------------------------------------------ | -------------------------------------------------------------------------- |
    | Same `UserHID` on the disputed order and prior orders  | One account made the purchases, across cookie clears and browser switches. |
    | Same `DeviceID` on the disputed order and prior orders | The same physical browser environment made the purchases, not a stranger.  |
    | Consistent `Country` across those visits               | No sudden geography change that would suggest a stolen card.               |
    | Clean or low `Score` on each visit                     | None of these purchases carried strong anonymity signals.                  |
    | Earlier orders that were never disputed                | A pattern of legitimate use from this device.                              |
    | `created_at` spanning weeks or months                  | A relationship, not a one-off hit-and-run.                                 |
  </Step>
</Steps>

<Warning>
  Reads through the account History API on `account.shieldlabs.ai` are **free**. The Management History path bills **1 request per returned row** (a lookup that matches nothing still bills **1 request**), so a 50-snapshot search costs 50 requests there; set `limit` to the smallest value that covers the order history you need to cite. Webhooks and dashboard exports stay free; the [Billing](/billing) page has the full breakdown.
</Warning>

## Test it

Confirm the continuity holds before you rely on it in a representment. Place a test order, note the `device_id` on its scored result, then clear cookies (or open an incognito window) on the same browser and place a second order. Both visits return the **same** `device_id` even though the cookie — and therefore the `visitor_id` — changed. Open the same site in a different browser or on a second machine and you will see a **new** `device_id`: that is the honest limit, a genuine buyer who switched devices between purchases will not show device continuity.

## What this evidence is, and is not

Device continuity is **supplementary** evidence. It strengthens a representment; it does not stand alone.

* **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 is a different `DeviceID`.** `DeviceID` is browser-bound, so the same buyer in Chrome and Safari on one laptop produces two `DeviceID`s with no continuity across them. Treat missing continuity as inconclusive on its own. To span a buyer's browsers, lead with `UserHID`, which is browser-independent.
* **The cardholder's bank rules on the dispute.** ShieldLabs supplies the device record, and your team writes the representment.

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. The [Patterns](/features/patterns) dashboard view can also show whether that device sits inside a "Many Accounts on One Device" relationship — useful triage context, though it is dashboard-only and not part of the webhook or History payload.

## Next

The real-time companion to this tutorial is the [payment fraud](/use-case/payment-fraud) checkout play, which scores the charge in the moment so fewer disputes ever reach this stage. Because a fraudulent purchase often starts with an [account takeover](/use-case/account-takeover), catching the new-device login earlier cuts off the charge before it happens. For the mechanics behind the evidence, the [Identifiers](/features/identification) reference explains how `DeviceID` stays durable, and the [History API](/api/server-api) and [Webhooks](/api/webhooks) references give the exact payload your dispute pipeline reads.

<CardGroup cols={2}>
  <Card title="Checkout protection" icon="cart-shopping" href="/use-case/payment-fraud">
    The real-time half: score the payment before the charge and gate it on the band.
  </Card>

  <Card title="Account takeover" icon="user-shield" href="/use-case/account-takeover">
    Catch the new-device login that often precedes the fraudulent purchase in the first place.
  </Card>
</CardGroup>
