> ## 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.

# Use Case Tutorials

> 22 end-to-end tutorials to detect and prevent fraud and abuse, measure traffic quality, and recognize trusted visitors with the snippet, webhook, and API.

Each tutorial is an end-to-end pattern: where to call the snippet, what arrives on the webhook (or the [API](/api/server-api)), and how your own code acts on the identity, the **Risk Score** and its `signals`, and the dashboard [Patterns](/features/patterns) that surface how one identity connects across many sessions — to prevent fraud and abuse, measure traffic quality, or recognize a trusted returning visitor. ShieldLabs scores the request; your application owns the verdict, so every threshold below lives in your code where you can read it, log it, and tune it.

The tutorials share the same building blocks:

* The [snippet](/setup/snippet) collects 100+ signals.
* A [webhook](/setup/webhooks) (or a [History API](/api/server-api) read) delivers the six [identifiers](/features/identification) and the explainable **Risk Score (0-100)** with its `signals`.
* You build your own logic on that score and its signals. The [Clean / Low / Medium / High bands](/features/risk-scoring) are a guide, not a rule.
* The dashboard [Patterns](/features/patterns) grade an identity over time, surfacing the many-accounts-on-one-device shapes a single request cannot see.

Read [Acting on the Risk Score](/guides/acting-on-risk-score) first if you want the decision skeleton these tutorials reuse.

## The logic every tutorial follows

Every tutorial is the same four moves; only the action and the thresholds change:

1. **Identify** at the action (signup, login, checkout). You get the durable **DeviceID**, and you pass your own **UserHID** so the account is tied to the device. Cleared cookies, incognito, and a new IP do not break that link.
2. **Read the anonymity** in real time. The **Risk Score** and its `signals` tell you whether this session is masked right now.
3. **Count on the device.** Several distinct UserHIDs on one DeviceID (or one Local IP) is not abuse on its own — how many is too many depends on your platform (an email provider may expect several accounts behind one device, while a bank expects one account per customer). Your code counts it at the action against your own threshold, and the dashboard **Patterns** surface the same correlation over time for what one request cannot see.
4. **Act in your own code.** Allow, challenge, review, or block at the action, and use Patterns exports as watchlists for offline review. ShieldLabs surfaces the evidence; your code owns every verdict.

<Note>
  New here? Start with the [Quickstart](/quickstart) to install the snippet and receive your first score, then [Acting on the Risk Score](/guides/acting-on-risk-score) for the decision pattern every tutorial below reuses.
</Note>

### Account and access

<CardGroup cols={2}>
  <Card title="New Account Fraud" icon="user-plus" href="/use-case/new-account-fraud">
    Catch multi-accounting and farm signups by joining the Risk Score with the DeviceID at registration.
  </Card>

  <Card title="Multi-Accounting" icon="users-rectangle" href="/use-case/multi-accounting">
    Link many accounts back to one person through a shared DeviceID and network, the pattern behind bonus, trial, and loyalty abuse.
  </Card>

  <Card title="Account Takeover" icon="user-shield" href="/use-case/account-takeover">
    Compare the login DeviceID against the account's history and step up when a known account hits a new device or country.
  </Card>

  <Card title="Credential Stuffing" icon="user-lock" href="/use-case/credential-stuffing">
    Score login anonymity and throttle on the durable DeviceID so rotated IPs stop resetting your limits.
  </Card>

  <Card title="Login and 2FA" icon="lock" href="/use-case/step-up-authentication">
    Call `forceCheckAuthenticatedUser` at login and step up to 2FA when the session scores Medium or High.
  </Card>

  <Card title="Account Sharing" icon="users" href="/use-case/account-sharing">
    See one account spread across many devices and countries, and enforce your own sharing policy.
  </Card>

  <Card title="Ban Enforcement" icon="ban" href="/use-case/ban-evasion">
    Key bans to the durable DeviceID so a cleared cookie or a fresh account does not let someone back in.
  </Card>

  <Card title="SMS Pumping" icon="comment-sms" href="/use-case/sms-pumping">
    Rate-limit verification SMS on the durable DeviceID so one session cannot flood your messaging bill with OTP toll fraud.
  </Card>
</CardGroup>

### Promotions and rewards

<CardGroup cols={2}>
  <Card title="Promo Abuse" icon="ticket" href="/use-case/promo-abuse">
    Count the accounts and redemptions tied to one device to stop signup-bonus and free-trial farming.
  </Card>

  <Card title="Bonus Abuse" icon="gift" href="/use-case/bonus-abuse">
    Catch repeat signup and deposit bonuses claimed through duplicate accounts on the same device.
  </Card>

  <Card title="Free-Trial Abuse" icon="hourglass-half" href="/use-case/free-trial-abuse">
    Spot new accounts cycling the same device to re-claim free trials and free-tier quotas.
  </Card>

  <Card title="Loyalty Fraud" icon="award" href="/use-case/loyalty-fraud">
    See points and tier rewards farmed across many linked identities instead of genuine activity.
  </Card>

  <Card title="Affiliate Fraud" icon="chart-line" href="/use-case/affiliate-fraud">
    Rank referral and promo conversions by anonymous-traffic share so masked clicks do not get paid out.
  </Card>

  <Card title="Sybil Attack" icon="diagram-project" href="/use-case/sybil-attack">
    Tie many wallets or identities back to one actor before an airdrop, vote, or quota pays out.
  </Card>

  <Card title="Coupon Abuse" icon="tag" href="/use-case/coupon-abuse">
    Tie each redemption to the DeviceID to enforce one-per-customer codes and refuse reused single-use coupons.
  </Card>
</CardGroup>

### Payments and content

<CardGroup cols={2}>
  <Card title="Checkout" icon="cart-shopping" href="/use-case/payment-fraud">
    Re-identify right before payment, then challenge or hold orders carrying strong anonymity signals.
  </Card>

  <Card title="Chargeback Dispute" icon="receipt" href="/use-case/chargeback-fraud">
    Reconstruct a buyer's device history into evidence against friendly-fraud chargebacks.
  </Card>

  <Card title="Paywall Enforcement" icon="newspaper" href="/use-case/paywall">
    Meter free views on the DeviceID, which clearing cookies or opening incognito cannot reset.
  </Card>

  <Card title="Regional Pricing" icon="globe" href="/use-case/regional-pricing">
    Read the anonymity signals and the IP country to catch VPN-masked region switching before you discount.
  </Card>

  <Card title="Card Testing" icon="credit-card" href="/use-case/card-testing">
    Anchor each checkout attempt to the durable DeviceID so your code can throttle card attempts and gate masked sessions.
  </Card>
</CardGroup>

### Traffic and experience

<CardGroup cols={2}>
  <Card title="Traffic Quality" icon="signal" href="/use-case/traffic-quality">
    Score every visit by source and channel to measure cost per real visitor, not per click.
  </Card>

  <Card title="Returning Visitor" icon="user-check" href="/use-case/returning-visitor">
    Recognize a clean returning device to cut friction for trusted visitors, the inverse of the fraud checks.
  </Card>
</CardGroup>

## How every tutorial is shaped

<Steps>
  <Step title="Identify at the right moment">
    Load the [snippet](/setup/snippet) on the relevant page. For sensitive actions (login, payment, withdrawal) call `forceCheckAnonymous` or `forceCheckAuthenticatedUser` to clear the session and re-score on the spot. Always pass a hashed account id (UserHID) to the authenticated calls, never a raw email.
  </Step>

  <Step title="Receive the identity and Risk Score">
    Each scored identification carries the six [identifiers](/features/identification) — the durable **DeviceID** among them — alongside the explainable **Risk Score** and its `signals`. Most tutorials key on that DeviceID, not the score alone: it is what links the "new" accounts a farm spins up. You receive it on the [webhook](/setup/webhooks), or read the same result from the [History API](/api/server-api) by `request_id` when a webhook is ever missed. The server waits up to \~60 seconds for an optional follow-up network check, then posts the final score once (about a second when no follow-up is expected). Verify `X-Shield-Signature` on the raw body and make your handler idempotent on `request_id`.
  </Step>

  <Step title="Decide in your own code">
    Branch on the band, and read each signal's numeric `weight` for the action context. A payment that lands in the High band warrants more than the raw number alone. Persist `request_id` plus your decision so the verdict is auditable.
  </Step>
</Steps>

A typical webhook payload the tutorials act on:

```json theme={null}
{
  "request_id": "8f1d0c2a-7b3e-4a9c-9d2f-1e6a5b4c3d21",
  "visitor_id": "c4a2e9b1-5f8d-4c3a-8e7b-2a1f0d9c8b76",
  "device_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "user_hid": "8a9f...hashed-account-id",
  "public_ip": { "ip": "203.0.113.42", "country": "US" },
  "os": "Mac OS X",
  "browser": "Chrome",
  "risk_score": 70,
    "signals": [
    { "name": "OS Mismatch", "weight": 60 },
    { "name": "Datacenter IP", "weight": 10 }
  ],
  "observed_at": "2026-06-16T10:00:00Z"
}
```

Here the score is 70 because two additive signals stack: an OS mismatch (60) and a datacenter IP (10).

<Note>
  The `signal` text is a free-form display label: it can include extra detail and may differ from the friendly names in the [Risk Score weight table](/features/risk-scoring), so don't branch on the exact string. Use the stable [`detection_flags`](/glossary#detection-flags) booleans to act on a specific signal, and weigh what each one means for your case — a 30 from one signal is not the same as a 30 from another.
</Note>

The shared decision skeleton, in your backend:

```js theme={null}
// Bands: Clean 0-9, Low 10-29, Medium 30-59, High 60-100.
function actionFor(score, signals) {
  // A rate-limit-banned snapshot can reach you scored 999. Guard the band logic.
  if (score > 100) return "block";

  // The weight any single signal contributed is in its numeric weight field.
  // Use the band, never the human-readable signal label, to drive decisions.
  const topSignal = Math.max(0, ...signals.map((d) => d.weight));

  if (score >= 60) return "challenge"; // High: block, review, or require verification
  if (score >= 30) return "review"; // Medium: step-up challenge or a second look
  if (score >= 10) return topSignal >= 30 ? "review" : "allow_log"; // Low, but a heavy single signal is worth a second look
  return "allow"; // Clean: pass through, no friction
}
```

<Note>
  The Risk Score is **0-100**, hard-capped at 100, in four bands: **Clean (0-9)**, **Low (10-29)**, **Medium (30-59)**, **High (60-100)**. A higher score means more anonymous or masked traffic, not a confirmed verdict. A legitimate visitor can score high (a corporate proxy, a VPN, or a privacy browser), so decide on Score plus `signals` plus action context, never the number alone, and tune thresholds gradually.
</Note>

<Warning>
  Webhooks are at-most-once with no retries and a roughly 1-second timeout. Make handlers idempotent on `request_id`, and for guaranteed reads poll the [History API](/api/server-api) instead of relying on a single delivery. Webhook delivery is free. History reads through `account.shieldlabs.ai` do not consume request balance.
</Warning>

## The shared webhook-cache helper

Every tutorial receives the score the same way: verify `X-Shield-Signature` on the raw body, respond fast, store the result by `request_id`, and let the request path read it back with a short timeout. This is the canonical `scoreCache` and `waitForScore` helper the individual tutorials link to instead of repeating it.

```js webhook-cache.js theme={null}
import crypto from 'crypto';
import express from 'express';

const scoreCache = new Map(); // use a shared store or your datastore in production
const SECRET = process.env.SHIELDLABS_WEBHOOK_SECRET; // whsec_… for this endpoint

// Keep the raw body so the bytes you hash match the bytes that were signed.
const app = express();
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));

// Webhook handler: verify, respond fast, then store keyed by request_id.
app.post('/shieldlabs/webhook', (req, res) => {
  const received = req.get('X-Shield-Signature') ?? '';
  const expected =
    'sha256=' +
    crypto.createHmac('sha256', SECRET).update(req.rawBody).digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).end();
  }

  const payload = req.body; // the webhook body, after signature verify
  // Idempotent on request_id: a redelivery can repeat it, so storing is safe
  // but do not double-apply business effects.
  res.status(200).end();
  // Cache the raw payload, so every tutorial reads the same shape the webhook
  // delivers: risk_score, signals, device_id, user_hid, public_ip, local_ip,
  // detection_flags, and the rest.
  scoreCache.set(payload.request_id, payload);
  setTimeout(() => scoreCache.delete(payload.request_id), 5 * 60 * 1000);
});

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Read path: poll the cache briefly, then fall back to a History API read.
async function waitForScore(requestId, timeoutMs) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    if (scoreCache.has(requestId)) return scoreCache.get(requestId);
    await sleep(100);
  }
  // Webhook never arrived in time: read the stored snapshot from the History API.
  const [snapshot] = await shieldHistory('request_id', requestId, 1);
  return snapshot ?? null;
}

const HISTORY_BASE = process.env.SHIELDLABS_API_URL ?? 'https://account.shieldlabs.ai/api';
const API_KEY = process.env.SHIELDLABS_API_KEY; // sec_… from dashboard API tab

/** Read snapshots by identifier. Returns the `data` array from the History API. */
async function shieldHistory(searchType, value, limit = 20) {
  const url = new URL(`${HISTORY_BASE}/v1/history/${searchType}/${encodeURIComponent(value)}`);
  url.searchParams.set('limit', String(limit));
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!res.ok) throw new Error(`history lookup failed: ${res.status}`);
  const body = await res.json();
  return body.data ?? [];
}
```

## Where to go next

If you have not wired up the snippet and a webhook yet, start with the [Quickstart](/quickstart) and [Setup](/setup). To understand what the score and its anonymity signals mean, read [Risk Score](/features/risk-scoring), [Signals](/features/anonymity-signals), and the dashboard [Patterns](/features/patterns). The [API overview](/api/overview) has the full payloads and endpoints behind these tutorials.
