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

# Run a use case tutorial

> Choose one standalone app, run its starter branch, then compare the completed ShieldLabs integration on final.

Each use case is an **independent application** with its own frontend, Fastify server, SQLite database, package lock and tests. There is no combined demo server or mode selector. The repository has two versions of each app:

* **starter**: the application without the ShieldLabs integration.
* **final**: the same application with browser identification and a server-owned decision.

Pick one scenario below. Run `starter` first to see the unprotected action, then follow its guide through the changes in `final`. Each finished app sends a request ID from the browser to its server, which reads the identification and makes the decision. You can run one folder without installing the other tutorials.

## Choose an application

| Tutorial | Folder |
| - | - |
| [New account fraud](/tutorials/one-trial-per-device) | `new-account-fraud` |
| [Checkout review](/tutorials/review-checkout) | `checkout-risk` |
| [Paywall enforcement](/tutorials/one-free-article) | `paywall` |
| [Account sharing](/tutorials/account-sharing) | `account-sharing` |
| [Account takeover](/tutorials/new-login-device) | `account-takeover` |
| [Ban evasion](/tutorials/ban-evasion) | `ban-evasion` |
| [Welcome bonus](/tutorials/welcome-bonus) | `bonus-abuse` |
| [Card testing](/tutorials/card-testing) | `card-testing` |
| [Chargeback evidence](/tutorials/chargeback-evidence) | `chargeback-dispute` |
| [Coupon abuse](/tutorials/coupon-limit) | `coupon-abuse` |
| [Credential stuffing](/tutorials/credential-stuffing) | `credential-stuffing` |
| [Loan application review](/tutorials/loan-application-review) | `loan-risk` |
| [Returning visitor personalization](/tutorials/returning-device) | `personalization` |
| [First-order promotion](/tutorials/first-order-promo) | `promo-abuse` |
| [Referral fraud](/tutorials/referral-pair) | `referral-fraud` |
| [Regional pricing](/tutorials/regional-discount) | `regional-pricing` |
| [SMS pumping](/tutorials/verification-sends) | `sms-pumping` |
| [Survey fraud](/tutorials/rewarded-survey) | `survey-fraud` |
| [Sybil claims](/tutorials/sybil-claim) | `sybil-attack` |
| [Web scraping](/tutorials/automated-scraping) | `web-scraping` |

## Run starter

You need Node.js 22 or later and npm. Clone the public [tutorial repository](https://github.com/ShieldLabs-ai/use-case-tutorials), then run one application:

```sh theme={null}
git clone https://github.com/ShieldLabs-ai/use-case-tutorials.git
cd use-case-tutorials
```

```sh theme={null}
git switch starter
cd new-account-fraud
npm ci --omit=dev
cp .env.example .env
npm run dev
```

Open [http://127.0.0.1:3000](http://127.0.0.1:3000). Replace `new-account-fraud` with the chosen folder. No ShieldLabs key is required on starter. Use only invented details or the supplied demo credentials.

## Add ShieldLabs

The finished app needs a Public Key in its browser and a Private API Key on its server. [Add the hostname](/setup/domains) under **Integration > Domains** and copy its matching keys from **Integration > API keys**. Adding a ShieldLabs domain does not create DNS or host your app: point that hostname at your own server, serve it over HTTPS and route requests to the Node process on `127.0.0.1:3000`. The `starter` works locally; a customer's key is not automatically accepted from `localhost`. Without an HTTPS host, you can still follow the code and run the local tests, but you cannot complete a live `final` identification.

<Steps>
  <Step title="1. Compare the two versions">
    Stop the starter server. From the repository root, compare the app you chose with its completed version. For example:

    ```sh theme={null}
    cd ..
    git diff starter origin/final -- new-account-fraud
    ```

    Use `origin/final` here: a fresh clone has the remote branch, but does not have a local `final` branch yet. The guide for each scenario names the browser, server and decision files to inspect.
  </Step>

  <Step title="2. Follow the request ID from browser to server">
    Each `final` app uses `@shieldlabs-ai/js` in its browser helper. A protected action sends its fresh `requestId` to the app server. For example, [Account sharing](/tutorials/account-sharing) starts a check when the login form receives focus:

    ```js theme={null}
    const identification = identifyOnFirstFocus(loginForm);
    const requestId = await identification.take();
    const data = await postJson('/api/login', {
      email, password, requestId, signOutOtherDevice
    });
    ```

    The Public Key is visible to the browser. The Private API Key is not sent with the form.
  </Step>

  <Step title="3. Verify and make the decision on the server">
    Each example's `server/shieldlabs.js` uses `@shieldlabs-ai/node` and the Private API Key to read History by that request ID. It refuses missing, stale, reused or unusable results before the scenario's own rule runs. In the [Account sharing server](https://github.com/ShieldLabs-ai/use-case-tutorials/blob/final/account-sharing/server/accounts.js), the login flow begins by checking the ID:

    ```js theme={null}
    const check = await verifyIdentification(requestId);
    if (!check.ok) {
      return { success: false, message: `Sign-in refused: ${check.message}` };
    }
    const deviceId = check.identification.device_id;
    ```

    The business rule is in that scenario's own server module. It uses a verified Device ID, account or Risk Score as needed; the server does not trust a risk result sent by the browser.
  </Step>

  <Step title="4. Run the completed version">
    Switch to `final` and reinstall dependencies in the chosen folder. The ignored `.env` from `starter` contains no key entries; add these two lines with the matching values:

    ```dotenv theme={null}
    SHIELDLABS_PUBLIC_KEY=your-public-key
    SHIELDLABS_API_KEY=sec_your_private_api_key
    ```

    Continuing the new-account-fraud example:

    ```sh theme={null}
    git switch final
    cd new-account-fraud
    npm ci --omit=dev
    npm start
    ```

    Your ignored `.env` is not replaced by a branch switch. Keep your own uncommitted work in another clone before switching. Open the completed app through your registered HTTPS hostname; `npm start` runs its Node server on loopback behind your reverse proxy.
  </Step>

  <Step title="5. Check the result">
    Perform the actions in your scenario's guide. Compare the request ID and Device ID with the matching identification in History. Check the decision on screen and the state after repeating the action. Use invented personal details; purchases, messages, rewards and challenges are simulated.
  </Step>
</Steps>

The server rereads History at least 11 seconds after `observed_at` as a precaution against early updates. This delay is not an explicit finality marker or a guarantee that the row cannot change later.

## Test and reset

From each app folder, run `npm run check` and `npm test`. The tests use an isolated database and synthetic History responses; they do not call live scoring or send real payments/messages. The optional `test-bot.js` requires dev dependencies (install with `npm ci`) and a separately configured browser-test environment.

`DEMO_ALLOW_RESET=1` permits resetting only the disposable demo's database. Schema differences between starter and final can recreate that database when you switch versions; do not store anything important there.

For a live check, record the real request IDs and corresponding History rows. Avoid unnecessary parallel identification runs. If the service rate-limits you, stop and check your account limits before retrying. A private window alone does not establish a new or identical Device ID: compare the observations.

## If a check does not finish

* **Final integration is not configured**: replace the placeholders in the private `.env` with the domain's actual Public Key and Private API Key. Restart the server and reload the page. The browser receives only a setup boolean and the Public Key, never the Private API Key.
* **The identification could not be verified**: an initialized request ID does not by itself prove that the snapshot was accepted and stored. Check the browser network/console and look for that same request ID in History. Do not substitute a fabricated successful identification.
* **HTTP 429 on challenge or snapshot**: stop live checks and automatic retries. Respect the service's retry guidance and verify the IP/domain/plan context before trying again. A 429 is not by itself proof of a specific ban reason. Do not switch IPs or change headers to evade a limit.
* **Cookies from another tutorial affect sign-in**: the apps namespace their own demo-session cookies. The scoring snippet's cookies are separate. Restarting or switching versions can clear that app's disposable database, so use its reset/login flow rather than deleting all browser cookies repeatedly.

The tutorials include only synthetic default form values and openly returned demo verification codes. Real emails, SMS, purchases and lending decisions are not performed. These defaults must not be carried into a production authentication or payment system.

## Before production

These are reference apps, not drop-in security modules. They simulate purchases, rewards, SMS and challenges. Add production authentication, authorization, shared atomic replay storage, durable state, request-to-session binding and a review process before adapting a rule to customer traffic. Their example thresholds are not ShieldLabs High-Risk Event thresholds. See [Acting on results](/guides/acting-on-risk-score).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.