Skip to main content
The payment step is where risk matters most: a masked or automated session at checkout is a stronger signal than the same session browsing a catalog. This tutorial reads the buyer’s account and a fresh Risk Score (0-100) right before the charge and maps the Risk Score to its band, so you can choose the action for each case.

What is payment fraud at checkout?

Payment fraud at checkout is the use of stolen cards, stolen accounts, or coordinated fake identities to push a charge through the payment step before it can be caught. The tell is concealment: the buyer hides behind a VPN, proxy, Tor, a datacenter IP, an anti-detect browser or an automated browser so the session cannot be traced back to a single person or device.

How ShieldLabs surfaces it

ShieldLabs ties each checkout to the buyer’s account and to everything that account is linked to. Pass the account’s hashed User HID and ShieldLabs links it to the devices, visitors and public and local IPs it uses, and detects Multi-accounting and Account takeover on it. A naive checkout trusts the cookie, the session or the buyer’s IP, and all three are trivial to reset. The Device ID holds through cleared cookies, incognito mode and IP changes, so a buyer who clears cookies or rotates to a fresh proxy IP between attempts still resolves to the same device. Underneath, the payment step itself is one identification: a Risk Score from 0 to 100 with every risk signal named and weighted, so a VPN, proxy, Tor, browser automation or an anti-detect browser on the paying session shows up by name. ShieldLabs stops payment fraud before the charge: it returns the Risk Score and every named risk signal on the payment step and detects High-Risk Events on the buyer’s account. You choose the action for each case: charge, step up to 3-D Secure or a one-time code, or hold for review.

Prevent payment fraud at checkout

Read the buyer’s account and the fresh Risk Score the moment the buyer reaches the payment step. The rule: charge when the payment step is Trusted and the account’s history is clean; step up when the payment step is Suspicious, when the device is new to an established account, or when risk signals such as Tor, Anti-detect Browser, Browser Automation or Abuser Flag fire; hold for review when the payment step or the account’s history is Dangerous, or when you marked the account after an Account takeover event reached you through the API, webhooks or the analytics dashboard. A buyer behind a fresh proxy IP or cleared cookies still resolves to the same device and the same account.

Build it

1

Create a ShieldLabs account and get your keys

Start Free with 5,000 identifications, one time, no credit card, or log in. In the analytics dashboard, add the domain you want to protect under Integration > Domains, then open Integration > API keys and copy its keys with the copy button next to each. The Public Key loads the snippet in the browser. Keep the server credentials on your backend: the Private API Key reads the History API, and each webhook endpoint has its own whsec_… signing secret. See API keys and Integration.
2

Force a fresh check at the payment step

On signed-in pages you already pass the hashed User HID with checkAuthenticatedUser. At the payment step you want a current read, so call forceCheckAuthenticatedUser: it runs an identification every time, keeps the current Session ID and restarts the five-minute window. A plain checkAuthenticatedUser runs at most one identification every five minutes for the same user within one visit, so a call right after another page’s check in the same visit would post nothing and the payment would reach your backend with no request ID. Pass a hashed or pseudonymous user id, never a raw email or account id.
checkout.html
The snippet POSTs the signals to rest.shieldlabs.ai automatically; installing the snippet covers the framework versions of the same dynamic-import pattern. For a guest checkout with no account, call forceCheckAnonymous instead; the handler below then skips the account binding and the account read. Card Testing covers guest checkouts in depth.
3

Read the buyer's account

The payment is one identification. The buyer’s account has a history. The shared accountView helper from the Use Case Tutorials reads the account’s identifications from the History API by user_hid, and its worst band shows how risky the account has been. To tell whether this payment comes from a device the account has used before, compare its Device ID with the devices on which the account completed a verified action, the trust list from Returning Visitor Recognition, rather than with every device in the account’s history: a taken-over session that browsed signed-in pages before paying already has identifications from its new device under this User HID. Pass a hashed User HID with checkAuthenticatedUser on every signed-in page. Users, account-level risk and all four High-Risk Events are built on it.
Read the buyer's account
A Dangerous history, or a payment from a device that is new to an established account, is the account-level shape of a taken-over account or a stolen card. History reads never count against your included identifications.Open the buyer’s user card in the analytics dashboard to see its band for the period, its High-Risk Events and each linked device, visitor and IP with the band of the identifications it shares with the account.
4

Receive the webhook and gate the charge

ShieldLabs POSTs one webhook per identification. Verify X-Shield-Signature on the raw body, then cache the result keyed by request_id so the checkout request can look it up. The shared waitForScore helper (defined once for every tutorial) does this read, polling the cache and falling back to the History API by request_id. Delivery is at-most-once with no retries, so the History fallback covers a dropped webhook. History reads and webhook delivery never count against your included identifications.Branch on the Risk Score of the payment and its band, which already fold in the risk signals, and on the account’s history. At the payment step, draw the band lines tighter than elsewhere.
checkout.js
NIL_DEVICE is the all-zero Device ID (00000000-0000-0000-0000-000000000000), exported by the shared helpers. An all-zero Device ID means no usable device signals reached ShieldLabs for that identification; the rate-limit marker (Risk Score 999) is one such case. Route it to review rather than allowing it, as the guard above does.ShieldLabs returns a Risk Score and every named risk signal on each identification, and detects High-Risk Events on your users. You choose the action for each case (charge, step up, review or block) and act on the result in your backend.In the analytics dashboard, the payment’s identification card shows its risk signals with their weights next to the risk of the account, device, visitor and IP behind it.
The Details and Risk of the identities in this call sections of one identification in the analytics dashboard: the Visitor ID, Device ID, Cookie ID, Session ID, User HID a91f3c7e5b2d4086 and Domain, a red Multi-accounting pill, then Visitor risk Suspicious, Device risk Dangerous, User risk Dangerous and IP risk Suspicious.The Details and Risk of the identities in this call sections of one identification in the analytics dashboard in the dark theme: the Visitor ID, Device ID, Cookie ID, Session ID, User HID a91f3c7e5b2d4086 and Domain, a red Multi-accounting pill, then Visitor risk Suspicious, Device risk Dangerous, User risk Dangerous and IP risk Suspicious.

One identification and the risk of the user, device, visitor and IP it belongs to, in the analytics dashboard.

5

Compare the countries behind a VPN

For a stolen-card buyer who hides their location, compare the two countries. The webhook carries two IP objects. public_ip is the public address and its country, which a VPN or proxy can put anywhere. local_ip is the Local IP: the address the browser itself reports, which can differ from the public IP behind a VPN or proxy and can expose the network behind the mask. local_ip.ip is empty when the Local IP was not captured. detection_flags.ip_mismatch is true whenever the two are different addresses. It is informational, adds nothing to the Risk Score and can be ordinary on mobile networks, so compare local_ip.country with public_ip.country rather than branching on the flag alone. A buyer whose public IP says one country while the Local IP sits in another is pretending to shop from somewhere else.
When the Local IP was not captured, the stun_not_checked risk signal can show that the network check did not complete, so you do not silently lose the comparison.For a hard rule that does not depend on the band, branch on the detection_flags object: its keys (vpn, tor, proxy, datacenter_ip, abuser, os_mismatch, anti_detect_browser, browser_automation, …) are stable booleans. Webhook signals[].name is a slug (antidetect_browser), not a display label.
6

Re-check on later sensitive actions

For a high-value order or a follow-up withdrawal, run another forceCheckAuthenticatedUser at that moment. Each sensitive action deserves its own fresh identification rather than a reused Risk Score.

Reading the Risk Score at checkout

The three bands and their ranges are defined in Risk Scoring; the full action playbook is in Acting on results. The payment step is a good place to draw the same band tighter than you would elsewhere: The Risk Score already folds in the risk signals, so you branch on the Risk Score and its band rather than on individual entries. Each entry in signals carries a stable slug in name and its weight; the table gives the slug and the label. The risk signals reference lists the full set with weights. Branch on detection_flags keys, which match the slugs except for one entry: the flag for Anti-detect Browser is anti_detect_browser. Proxy, Datacenter IP, and Abuser Flag are each low weight and stack: a buyer on a flagged datacenter proxy with abuser reputation reaches the Suspicious band from these three together, where any one alone would not. Tor, JavaScript Disabled, OS Mismatch, Anti-detect Browser and Browser Automation are the high-weight single signals that push straight into Dangerous.
Read a high Risk Score together with its named risk signals and the user’s history. A real customer on a corporate VPN can reach the Suspicious band; the signals show why, and you choose the action for each case. Corporate VPNs, privacy browsers and iCloud Private Relay all raise the Risk Score for real customers, so decide on the Risk Score, the signals, the account’s history and the action context together, and tune your cutoffs gradually. A vpn or privacy_relay signal alone is weaker evidence than tor, antidetect_browser or browser_automation.

High-Risk Events on the buyer’s account

ShieldLabs detects Multi-accounting and Account takeover on your users directly. Multi-accounting: several accounts run by one person, linked through the devices and network they share. Account takeover: an existing account appearing in a new environment that points to someone else using it. Each detection carries Medium or High confidence, depending on the combination of evidence, on its own axis next to the Risk Score, so an account can be Trusted on every identification and still carry an event. High-Risk Events are available in the analytics dashboard, the API and webhooks, and need the hashed User HID you pass with checkAuthenticatedUser. The Risk Score and risk signals of the identification remain the input at checkout. When a High-Risk Event arrives for a buyer through the API or webhooks, or when you review it in the analytics dashboard, act on the account. You choose the action for each case, for example marking the account in your own system so its next payment steps up or goes to review, as the marked check above does. Acting on results gives a starting point for each event.
The user card header for User HID 5e0c2b7d91a4f3c6 in the analytics dashboard: the Dangerous band pill, a red Account takeover pill (High confidence), 9 identifications (7 Trusted, 1 Suspicious, 1 Dangerous) and Details with 2 linked devices and 2 linked countries.The user card header for User HID 5e0c2b7d91a4f3c6 in the analytics dashboard in the dark theme: the Dangerous band pill, a red Account takeover pill (High confidence), 9 identifications (7 Trusted, 1 Suspicious, 1 Dangerous) and Details with 2 linked devices and 2 linked countries.

An account with an Account takeover event in the analytics dashboard.

Test it

Confirm the Device ID holds before you wire your cutoffs to live charges. Run a checkout, note the device_id from the webhook, then repeat it in an incognito window and after clearing cookies: the cookie_id and visitor_id change each time, but the device_id stays the same, which links one buyer across “fresh” sessions. A second browser on the same machine is a new Device ID; the User HID ties that checkout to the same account. To see the Risk Score react, repeat the checkout through a VPN or proxy and watch signals gain a vpn or proxy entry. Then open the buyer’s user card in the analytics dashboard: Linked devices lists both browsers, each with the band of the buyer’s identifications on it.

Next steps

When a disputed charge lands weeks later, the account and the Device ID you scored here become chargeback-dispute evidence. Upstream, the same fresh-check pattern guards a suspicious login with step-up authentication and a new account at signup, and the Multi-accounting event surfaces one buyer running many accounts.

Acting on results

Turn the Risk Score, its signals and the account’s history into allow, challenge, review, and block logic in your app.

Risk signals

Every risk signal that can appear in signals, in plain language, with its weight.

The Risk Score

How the Risk Score from 0 to 100 is built, what signals carries, and the band definitions.

Chargeback evidence

The after-the-sale half: reconstruct the buyer’s account and device history into a dispute package.