# Documentation

Welcome to your team’s developer platform

<figure><picture><source srcset="/files/LvXYZo0uYCiEgUYPqrsD" media="(prefers-color-scheme: dark)"><img src="https://1101552469-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FTd3PVPFz34p4DBREd9lj%2Fuploads%2FWkekb3gI42XTR0P93Pvb%2Fblue.svg?alt=media&amp;token=8790e57c-37f4-4ce8-9736-26312dba593e" alt=""></picture><figcaption></figcaption></figure>

<h4 align="center">Advanced fraud detection and risk assessment for modern applications</h4>

<p align="center">Detect bots, VPNs, browser tampering, and suspicious behaviour with enterprise-grade accuracy.</p>

<p align="center"></p>

{% columns %}
{% column width="50%" %}

### Stop fraud before it costs you

Built for teams who can't afford to get security wrong. Crystal-clear endpoints, proven examples, and rock-solid authentication get you from zero to protected in one commit.

**Deploy fast. Sleep better.**

<a href="https://docs.guardianstack.ai/documentation/" class="button primary" data-icon="rocket-launch">Get started</a> <a href="https://docs.guardianstack.ai/help-center/" class="button secondary" data-icon="terminal">Help Center</a>
{% endcolumn %}

{% column width="50%" %}
{% code title="Your Frontend" overflow="wrap" %}

```javascript
// frontend/guardian.ts
import { loadAgent } from '@guardianstack/guardian-js';

// 1) Initialize once at app startup
const guardian = await loadAgent({
  siteKey: 'site_XXXXXXXX',
});

// 2) Trigger an identification exactly where it matters (login, signup, checkout)
const res = await guardian.get();

// 3) Extract the requestId and send it to your backend for risk evaluation
const requestId = res?.requestId;

```

{% endcode %}

{% code title="Your Backend" overflow="wrap" %}

```javascript
// server/guardian.ts
import { createGuardianClient } from '@guardianstack/guardianjs-server';

// 1) Create the server client with your secret (server-only; never expose in the browser)
const guardian = createGuardianClient({ secret: 'sec_XXXXXXXX' });

// 2) Fetch the processed event by requestId (includes IP intel + detections)
const event = await guardian.getEvent(requestId);

// 3) Make a simple allow/deny decision based on detections
const risky = Boolean(
  event.botDetection?.detected ||
  event.tampering?.detected ||
  event.virtualization?.detected ||
  event.incognito?.detected ||
  event.privacySettings?.detected ||
  event.vpn?.detected
);

if (risky) { 
  /* High risk: deny or require step-up (CAPTCHA/OTP/KYC) */ 
} else { 
  /* Low risk: proceed */ 
}
```

{% endcode %}
{% endcolumn %}
{% endcolumns %}

<h3 align="center">Learn more about Guardian</h3>

<p align="center">Transform from integration to expertise with in-depth guides, advanced techniques, and the security patterns that keep the bad actors out and your users protected.</p>

<p align="center"> <a href="https://docs.guardianstack.ai/documentation/" class="button primary" data-icon="book">Documentation</a></p>

<h2 align="center"></h2>

<h2 align="center"></h2>


# Welcome to Guardian

Every day, fraudsters get more sophisticated. Your defences should too. These docs will show you how to implement enterprise-grade fraud detection that's powerful enough for the biggest platforms, yet simple enough to deploy in minutes.

**Start protecting your platform today.**

### Jump right in

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><i class="fa-bolt">:bolt:</i></h4></td><td><strong>Quickstart</strong></td><td></td><td></td><td></td><td><a href="/documentation/getting-started/quickstart">Quickstart</a></td></tr><tr><td><i class="fa-shield">:shield:</i></td><td><strong>Guardian Engine</strong></td><td></td><td></td><td></td><td><a href="/documentation/getting-started/guardian-engine">Guardian Engine</a></td></tr></tbody></table>


# Quickstart

Stop fraud in under 10 minutes with Guardian's comprehensive fraud detection platform.

Guardian Stack is a fraud detection platform that helps businesses protect their applications from bots, account takeover, VPN abuse, browser tampering, and sophisticated fraud attempts. Guardian Stack capabilities include:

{% stepper %}
{% step %}

### **Real-time fraud detection**

Identify bots, VPNs, incognito browsing, and browser tampering with enterprise-grade accuracy.
{% endstep %}

{% step %}

### Risk assessment

Get actionable fraud signals and confidence scores for every visitor interaction.
{% endstep %}

{% step %}

### Developer-friendly

Simple 3-line integration that works with any stack.
{% endstep %}
{% endstepper %}

### 1. Get your API keys

Sign up for Guardian Stack to get your site key and secret key.

* Copy your Site Key (for client-side integration)
* Copy your Secret Key (for server-side event retrieval)

### 2. Add the client-side agent

Install the Guardian JS SDK to collect fraud signals from browsers:

```bash
npm install @guardianstack/guardian-js
# or
yarn add @guardianstack/guardian-js
```

Usage:

```javascript
import { loadAgent } from '@guardianstack/guardian-js';

// Initialize the agent once when your app starts
const guardian = await loadAgent({ 
  siteKey: 'YOUR_SITE_KEY' 
});

// Call .get() when you need fraud protection (login, signup, payment, etc.)
// Don't call on every page load - only for critical user actions
const response = await guardian.get();

const requestId = response?.requestId;

// Send requestId to your server for fraud analysis
// Your backend will use this ID to get the full fraud assessment
```

{% hint style="warning" %}
**EU/EEA deployments.** If the website or application accessing the agent is accessible to users in the European Economic Area, obtain prior consent under Article 5(3) of the ePrivacy Directive before calling `loadAgent()`. A simple pattern: gate the `loadAgent()` call behind your consent manager's fraud-prevention or security category.

**UK deployments.** The UK Data (Use and Access) Act 2025 treats fraud-prevention fingerprinting as strictly necessary under PECR. A consent gate is generally not required for UK-only deployments, but a privacy notice disclosure remains necessary.

**US deployments.** No prior consent is generally required under US state privacy laws, but disclose the processing in your privacy notice.
{% endhint %}

### 3. Get fraud signals on your server

Install the server SDK to analyze fraud signals:

```javascript
import {
  createGuardianClient,
  isBot,
  isVPN,
  isTampering,
  isIncognito,
  isVirtualized,
} from '@guardianstack/guardianjs-server';

// Initialize the client once in your app with your secret key
const client = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY
});

// In your API route handler (e.g., /api/login, /api/signup)
// Use the requestId from the client-side guardian.get() call
const event = await client.getEvent(requestId);

// Get simple boolean fraud indicators for quick decisions
const risks = {
  bot: isBot(event),                 // Selenium, Puppeteer, headless browsers
  vpn: isVPN(event),                 // VPN/proxy usage detection
  tampering: isTampering(event),     // Anti-detect browsers, spoofed APIs
  incognito: isIncognito(event),     // Private/incognito browsing mode
  virtualized: isVirtualized(event)  // VM or emulated environments
};

// Now make your fraud decision based on these risks
```

You can also call the Guardian Stack API directly from your backend without using the server SDK:

```bash
curl --location \
  --retry 5 \
  --retry-max-time 60 \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_GUARDIAN_SECRET_KEY' \
  'https://api.guardianstack.ai/request/event/YOUR_EVENT_ID'
```

Make sure to replace `YOUR_EVENT_ID` and `YOUR_GUARDIAN_SECRET_KEY`.

Example response:

```json
{
    "identification": {
        "id": "example-request-id",
        "visitorId": "example-visitor-id",
        "ip": "192.0.2.1",
        "timestamp": "2025-01-01T12:00:00.000Z",
        "url": "https://example.com/",
        "location": {
            "is_eu_member": true,
            "calling_code": "1",
            "currency_code": "USD",
            "continent": "NA",
            "country": "United States",
            "country_code": "US",
            "state": "California",
            "city": "San Francisco",
            "latitude": 37.7749,
            "longitude": -122.4194,
            "zip": "94103",
            "timezone": "America/Los_Angeles",
            "local_time": "2025-01-01T04:00:00-08:00",
            "is_dst": false
        },
        "browser": {
            "browserName": "Chrome",
            "browserMajorVersion": "120",
            "browserFullVersion": "120.0.0.0",
            "platform": "Win32",
            "os": "Windows",
            "osVersion": "10",
            "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
        }
    },
    "botDetection": {
        "detected": true,
        "score": 0.85,
        "automationSignalsPresent": true,
        "indicators": [
            {
                "source": "automation_api",
                "confidence": "high"
            }
        ]
    },
    "ipInfo": {
        "ip": "192.0.2.1",
        "rir": "ARIN",
        "is_bogon": false,
        "is_mobile": false,
        "is_satellite": false,
        "is_crawler": true,
        "is_datacenter": true,
        "is_tor": false,
        "is_proxy": true,
        "is_vpn": true,
        "is_abuser": false,
        "company": {
            "name": "Example Hosting",
            "abuser_score": "0.2 (Low)",
            "domain": "example.com",
            "type": "hosting",
            "network": "192.0.2.0/24"
        },
        "abuse": {
            "name": "Abuse Department",
            "address": "123 Example St, San Francisco, CA",
            "email": "abuse@example.com",
            "phone": "+1-555-123-4567"
        },
        "asn": {
            "asn": 12345,
            "abuser_score": "0.1 (Low)",
            "route": "192.0.2.0/24",
            "descr": "EXAMPLE-ASN",
            "country": "us",
            "active": true,
            "org": "Example Organization",
            "domain": "example.com",
            "abuse": "abuse@example.com",
            "type": "hosting",
            "created": "2000-01-01",
            "updated": "2020-01-01",
            "rir": "ARIN"
        },
        "location": {
            "is_eu_member": true,
            "calling_code": "1",
            "currency_code": "USD",
            "continent": "NA",
            "country": "United States",
            "country_code": "US",
            "state": "California",
            "city": "San Francisco",
            "latitude": 37.7749,
            "longitude": -122.4194,
            "zip": "94103",
            "timezone": "America/Los_Angeles",
            "local_time": "2025-01-01T04:00:00-08:00",
            "local_time_unix": 1735837200,
            "is_dst": false
        },
        "elapsed_ms": 0.5
    },
    "vpn": {
        "detected": true,
        "confidence": "high",
        "browserTimezone": "Europe/London",
        "ipTimezone": "America/Los_Angeles",
        "timezoneDifference": 8
    },
    "tampering": {
        "detected": true,
        "anomalyScore": 0.75,
        "antiDetectBrowser": true,
        "indicators": [
            {
                "source": "browser_api_inconsistency",
                "severity": "high"
            },
            {
                "source": "viewport_screen_mismatch",
                "severity": "medium"
            }
        ]
    },
    "privacySettings": {
        "detected": true,
        "score": 0.6,
        "indicators": [
            {
                "source": "storage_blocked",
                "confidence": "medium"
            }
        ]
    },
    "virtualization": {
        "detected": true,
        "confidence": "high",
        "indicators": [
            {
                "source": "vm_hardware_detected",
                "confidence": "high"
            },
            {
                "source": "performance_metrics",
                "confidence": "medium"
            }
        ]
    },
    "incognito": {
        "detected": true,
        "score": 0.9,
        "indicators": [
            {
                "source": "storage_anomaly",
                "confidence": "high"
            }
        ]
    },
    "velocity": {
        "5m": 25,
        "1h": 120,
        "24h": 500
    }
}
```

### 4. Make fraud decisions

Use the fraud signals to protect your application:

```javascript
// Example fraud prevention logic in your API endpoint
// Adjust thresholds based on your risk tolerance and user experience goals

if (risks.bot || risks.tampering || risks.virtualized) {
  // High-risk indicators: automated attacks, spoofed environments
  // These almost always indicate malicious intent
  return { action: 'block', reason: 'Suspicious automation detected' };
}

if (risks.vpn && risks.incognito) {
  // Medium-risk: privacy tools + anonymization
  // Could be legitimate privacy-conscious users or fraud attempts
  return { action: 'challenge', reason: 'Additional verification required' };
}

// Low-risk: Normal user behavior
// Let the request proceed without friction
return { action: 'allow' };

// Pro tip: You can also combine with other signals like:
// - Geographic anomalies (user suddenly in different country)
// - Velocity checks (too many requests too quickly)
// - Account history (new account vs established user)
```

### 🎉 You're protected!

Congratulations! You now have enterprise-grade fraud detection running in your application. Here's what you've accomplished:

✅ Real-time fraud detection - Your app now identifies bots, VPNs, and suspicious behavior

✅ Risk-based decisions - You can block, challenge, or allow users based on fraud signals

✅ Production-ready - Your integration is secure and scales with your traffic


# Guardian Engine

The fraud detection engine that powers your protection

Guardian Stack's server API transforms raw browser signals into actionable fraud intelligence. When your client calls `guardian.get()`, our processing engine analyzes hundreds of signals to detect bots, VPNs, browser tampering, and sophisticated fraud attempts.

### How Server Processing Works

**The magic happens server-side** where we can safely perform intensive analysis without impacting user experience or exposing detection logic to potential attackers.

<figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FBoA46HmPEa8SGgJhtqpJ%2Fdiagram-export-10-10-2025-15_06_21.png?alt=media&amp;token=86efff80-30bf-4c60-b04b-21e34f4b3ff7" alt=""><figcaption></figcaption></figure>

***

### Complete Event Response Structure

#### Core Identification

```json
{
  "identification": {
    "id": "1760039992053.8tuafsur",      // Your unique event reference
    "ip": "81.56.52.67",                 // Source IP for threat analysis
    "timestamp": "2025-10-09T19:59:52.364Z", // When the risk was assessed
    "url": "http://localhost:4176/",      // Context of user action
    "location": { /* geolocation data */ },
    "browser": { /* parsed browser info */ }
  }
}
```

**Fraud Detection Usage:**

* **`id`** - Reference this in your fraud logs and dispute resolution
* **`ip`** - Cross-reference with your existing IP reputation lists
* **`location`** - Flag geographic anomalies (user in US, IP in Russia)
* **`browser`** - Detect impossible browser configurations

***

#### Bot Detection Analysis

```json
{
  "botDetection": {
    "detected": false,                    // 🚨 BLOCK if true
    "score": 0,                          // 0-100: Higher = more bot-like
    "automationSignalsPresent": false,   // Selenium/Puppeteer detected
    "indicators": []                     // Why we flagged this as a bot
  }
}
```

**What This Means for Fraud:**

* **`detected: true`** → Almost certainly automated traffic. **Recommended action: BLOCK**
* **`score > 80`** → Very high bot probability. Consider challenging or blocking
* **`score 40-80`** → Suspicious but inconclusive. Add friction or monitor
* **`automationSignalsPresent: true`** → Definitive automation tools detected

**Common Bot Indicators You'll See:**

* `webdriver` → Selenium automation (credential stuffing, account creation bots)
* `headless_chrome` → Headless browser (scraping, automated purchases)
* `puppeteer` → Advanced automation (sophisticated fraud attempts)

***

#### IP Intelligence Deep Dive

```json
{
  "ipInfo": {
    "is_datacenter": false,              // 🚨 High fraud risk if true
    "is_tor": false,                     // 🚨 Anonymous network usage
    "is_proxy": false,                   // 🚨 Traffic routing detected
    "is_vpn": false,                     // ⚠️ Privacy tool or fraud
    "is_abuser": false,                  // 🚨 Known malicious IP
    "company": {
      "type": "isp",                     // vs "hosting" (red flag)
      "abuser_score": "0.0007 (Low)"    // Historical fraud patterns
    }
  }
}
```

**Critical Fraud Signals:**

* **`is_datacenter: true`** → Traffic from hosting providers (bots, farms)
* **`is_tor: true`** → Maximum anonymity seeking (high fraud correlation)
* **`is_abuser: true`** → IP has fraud history across the internet
* **`company.type: "hosting"`** → Not residential internet (suspicious for consumers)

**Risk Assessment Guide:**

```javascript
// High risk combinations
if (ipInfo.is_datacenter && ipInfo.is_proxy) {
  // Hosting provider + proxy = bot farm
}

if (ipInfo.is_tor || ipInfo.is_abuser) {
  // Known bad actor infrastructure
}
```

***

#### VPN Detection Engine

```json
{
  "vpn": {
    "detected": false,                   // Privacy tool or fraud?
    "confidence": "none",                // How certain we are
    "browserTimezone": "Europe/Rome",    // What browser claims
    "ipTimezone": "Europe/Rome",         // What IP location shows
    "timezoneDifference": 0              // Hours apart (key fraud signal)
  }
}
```

**Fraud Detection Intelligence:**

* **`detected: true`** → User hiding real location (why?)
* **`timezoneDifference > 6`** → Major geographic inconsistency
* **`confidence: "high"`** → Very likely VPN usage

**Fraud Context Matters:**

```javascript
// Legitimate privacy vs fraud indicators
if (vpn.detected && user.isNewAccount) {
  // New user + VPN = suspicious
} else if (vpn.detected && user.hasHistory) {
  // Existing user + VPN = privacy conscious
}
```

***

#### Browser Tampering Detection

```json
{
  "tampering": {
    "detected": true,                    // 🚨 Environment manipulation
    "anomalyScore": 0.55,               // 0-1: Abnormality level  
    "antiDetectBrowser": false,         // Specialized fraud tool
    "indicators": [
      {
        "source": "vendor_mismatch",     // Browser lying about identity
        "severity": "medium"
      }
    ]
  }
}
```

**What Tampering Means:**

* **`detected: true`** → Browser has been modified to avoid detection
* **`antiDetectBrowser: true`** → Professional fraud tools detected
* **`anomalyScore > 0.7`** → Major inconsistencies in browser environment

**Tampering = Fraud Intent:**

* Users don't accidentally modify browsers
* Tampering tools are expensive and specialized
* High correlation with fraud attempts

**Key Indicators:**

* `vendor_mismatch` → Browser claims to be Chrome but acts like Firefox
* `canvas_spoofing` → Artificial fingerprint to avoid tracking
* `webgl_anomalies` → Graphics inconsistencies (virtual environments)

***

#### Privacy Settings vs. Fraud

```json
{
  "privacySettings": {
    "detected": false,                   // Privacy tools active
    "score": 0,                         // Intensity of privacy measures
    "indicators": []                    // What privacy tools found
  }
}
```

**The Privacy Dilemma:**

* **Legitimate privacy users** → Ad blockers, privacy browsers, VPNs for safety
* **Fraud actors** → Same tools to avoid detection

**Smart Fraud Detection:**

```javascript
// Don't punish privacy, but be aware
if (privacySettings.detected && !botDetection.detected) {
  // Likely legitimate privacy-conscious user
} else if (privacySettings.detected && tampering.detected) {
  // Privacy tools + tampering = fraud attempt
}
```

***

#### Virtualization Red Flags

```json
{
  "virtualization": {
    "detected": false,                   // 🚨 VM environment detected
    "confidence": "none",                // Detection certainty
    "indicators": []                    // VM signatures found
  }
}
```

**Why VMs Matter for Fraud:**

* **Scalability** → Easy to spin up hundreds of fraud instances
* **Isolation** → No risk to fraudster's real machine
* **Throwaway** → Delete evidence after attack

**VM Detection = High Fraud Risk:**

* Consumer users rarely run VMs for browsing
* Professional fraud operations always use VMs
* Combined with other signals = almost certain fraud

***

#### Incognito Detection Intelligence

```json
{
  "incognito": {
    "detected": false,                   // Private browsing mode
    "score": 0,                         // Detection confidence
    "indicators": []                    // Technical signatures
  }
}
```

**Incognito Fraud Context:**

* **By itself** → Normal privacy behavior
* **With VPN + new account** → Avoiding identification
* **With bot signals** → Automated incognito sessions

***

#### Request IP Velocity Intelligence

```json
{
  "velocity": {
    "5m": 3,
    "1h": 17,
    "24h": 68
  }
}
```

* **What it is**: Number of requests from the same IP (scoped to your `siteKey` when provided) within rolling 5-minute, 1-hour, and 24-hour windows.
* **How it's computed**:
  * Server-side counts within \[now − window, now].
  * Includes the current event (+1), so first-ever event yields at least 1.
  * Uses server time; not affected by client timezones.
* **Why it matters**: Sudden spikes strongly correlate with automation, credential stuffing, scraping, and abuse.

{% hint style="danger" %}
**Default High-Risk Thresholds**

**5m ≥ 25** OR **1h ≥ 150** OR **24h ≥ 1000** → high velocity
{% endhint %}

**Fraud Detection Usage**

* **High burst traffic**: Treat as a strong indicator of automation.
* **Per-site scope**: When `siteKey` is present, velocity is computed per site; otherwise global per IP.
* **Noise considerations**:
  * Popular endpoints can have legitimate spikes; combine with bot, datacenter, or tampering signals for confidence.
  * Logged-in returning users with high velocity may be power users; tune responses accordingly.

**Recommended Actions**

* **Block** when high velocity co-occurs with any of:
  * Datacenter IP, TOR, known abuser IP
  * Bot indicators or browser tampering
* **Challenge** when high velocity is isolated but the user is new/anonymous
* **Allow but monitor** for high velocity from known, low-risk customers

Here are the docs for Visitor Velocity Intelligence that you can copy:

***

### **Visitor Velocity Intelligence**

```json
{
  "visitorVelocity": {
    "5m": 3,
    "1h": 17,
    "24h": 68,
    "7d": 241
  }
}
```

**What it is:** Number of requests from the same `visitorId` (scoped to your siteKey when provided) within rolling 5-minute, 1-hour, 24-hour, and 7-day windows.

**How it's computed:**

* Server-side counts within \[now − window, now].
* Includes the current event (+1), so first-ever event yields at least 1.
* Uses server time; not affected by client timezones.
* Only computed when a `visitorId` is available.

{% hint style="info" %}
**Why it matters:** Tracks request patterns per stable visitor identity, catching automation, credential stuffing, and abuse even when the attacker rotates IPs. Complements IP-based velocity to detect account-level or device-level spikes.
{% endhint %}

**Compared to Request Velocity:**

* `velocity`: Counts by IP address (may aggregate many visitors behind NAT/proxies).
* `visitorVelocity`: Counts by stable `visitorId` (resilient to IP rotation for the same visitor).

{% hint style="danger" %}
**Suggested High-Risk Thresholds**

5m ≥ 10 OR 1h ≥ 60 OR 24h ≥ 500 OR 7d ≥ 2000 → high visitor velocity
{% endhint %}

### Fraud Decision Framework

#### Immediate Block Scenarios

```javascript
// These combinations = definite fraud
const shouldBlock = (
  event.botDetection.detected ||
  event.ipInfo.is_abuser ||
  event.ipInfo.is_tor ||
  (event.tampering.detected && event.tampering.antiDetectBrowser) ||
  (event.ipInfo.is_datacenter && event.botDetection.score > 60)
);
```

#### Challenge/Additional Verification

```javascript
// Suspicious but not definitive
const shouldChallenge = (
  (event.vpn.detected && user.isNewAccount) ||
  (event.tampering.detected && !event.tampering.antiDetectBrowser) ||
  (event.ipInfo.is_datacenter && event.virtualization.detected) ||
  event.botDetection.score > 70
);
```

> #### We also offer our [server-side SDK](https://www.npmjs.com/package/@mugshotlabs/guardianjs-server) which includes many helper functions to save time in destructuring the event data.

***


# Editor

GitBook has a powerful block-based editor that allows you to seamlessly create, update, and enhance your content.

<figure><img src="https://gitbookio.github.io/onboarding-template-images/editor-hero.png" alt=""><figcaption></figcaption></figure>

### Writing content

GitBook offers a range of block types for you to add to your content inline — from simple text and tables, to code blocks and more. These elements will make your pages more useful to readers, and offer extra information and context.

Either start typing below, or press `/` to see a list of the blocks you can insert into your page.

### Add a new block

{% stepper %}
{% step %}

#### Open the insert block menu

Press `/` on your keyboard to open the insert block menu.
{% endstep %}

{% step %}

#### Search for the block you need

Try searching for “Stepper”, for exampe, to insert the stepper block.
{% endstep %}

{% step %}

#### Insert and edit your block

Click or press Enter to insert your block. From here, you’ll be able to edit it as needed.
{% endstep %}
{% endstepper %}


# Markdown

GitBook supports many different types of content, and is backed by Markdown — meaning you can copy and paste any existing Markdown files directly into the editor!

<figure><img src="https://gitbookio.github.io/onboarding-template-images/markdown-hero.png" alt=""><figcaption></figcaption></figure>

Feel free to test it out and copy the Markdown below by hovering over the code block in the upper right, and pasting into a new line underneath.

```markdown
# Heading

This is some paragraph text, with a [link](https://docs.gitbook.com) to our docs. 

## Heading 2
- Point 1
- Point 2
- Point 3
```

{% hint style="info" %}
If you have multiple files, GitBook makes it easy to import full repositories too — allowing you to keep your GitBook content in sync.
{% endhint %}


# Images & media

GitBook allows you to add images and media easily to your docs. Simply drag a file into the editor, or use the file manager in the upper right corner to upload multiple images at once.

<figure><img src="https://gitbookio.github.io/onboarding-template-images/images-hero.png" alt=""><figcaption><p>Add alt text and captions to your images</p></figcaption></figure>

{% hint style="info" %}
You can also add images simply by copying and pasting them directly into the editor — and GitBook will automatically add it to your file manager.
{% endhint %}


# Interactive blocks

In addition to the default Markdown you can write, GitBook has a number of out-of-the-box interactive blocks you can use. You can find interactive blocks by pressing `/` from within the editor.

<figure><img src="https://gitbookio.github.io/onboarding-template-images/interactive-hero.png" alt=""><figcaption></figcaption></figure>

### Tabs

{% tabs %}
{% tab title="First tab" %}
Each tab is like a mini page — it can contain multiple other blocks, of any type. So you can add code blocks, images, integration blocks and more to individual tabs in the same tab block.
{% endtab %}

{% tab title="Second tab" %}
Add images, embedded content, code blocks, and more.

```javascript
const handleFetchEvent = async (request, context) => {
    return new Response({message: "Hello World"});
};
```

{% endtab %}
{% endtabs %}

### Expandable sections

<details>

<summary>Click me to expand</summary>

Expandable blocks are helpful in condensing what could otherwise be a lengthy paragraph. They are also great in step-by-step guides and FAQs.

</details>

### Embedded content

{% embed url="<https://www.youtube.com/watch?v=YILlrDYzAm4>" %}

{% hint style="info" %}
GitBook supports thousands of embedded websites out-of-the-box, simply by pasting their links. Feel free to check out which ones[ are supported natively](https://iframely.com).
{% endhint %}


# Integrations

GitBook integrations allow you to connect your GitBook spaces to some of your favorite platforms and services. You can install integrations into your GitBook page from the *Integrations* menu in the top left.

<figure><img src="https://gitbookio.github.io/onboarding-template-images/integrations-hero.png" alt=""><figcaption></figcaption></figure>

### Types of integrations

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th></tr></thead><tbody><tr><td><strong>Analytics</strong></td><td>Track analytics from your docs</td><td><a href="https://www.gitbook.com/integrations#analytics">https://www.gitbook.com/integrations#analytics</a></td><td></td><td></td></tr><tr><td><strong>Support</strong></td><td>Add support widgets to your docs</td><td><a href="https://www.gitbook.com/integrations#support">https://www.gitbook.com/integrations#support</a></td><td></td><td></td></tr><tr><td><strong>Interactive</strong></td><td>Add extra functionality to your docs</td><td><a href="https://www.gitbook.com/integrations#interactive">https://www.gitbook.com/integrations#interactive</a></td><td></td><td></td></tr><tr><td><strong>Visitor Authentication</strong></td><td>Protect your docs and require sign-in</td><td><a href="https://www.gitbook.com/integrations#visitor-authentication">https://www.gitbook.com/integrations#visitor-authentication</a></td><td></td><td></td></tr></tbody></table>


# Visitor Identification

Accurately identify visitors on your website to prevent fraud, account takeovers, and abuse.

GuardianStack generates a stable, server-issued `visitorId` that persists across sessions, incognito windows, and browser updates. Unlike simple cookies or local identifiers, our visitor ID is computed using advanced signal processing and fuzzy matching on the server, making it highly resistant to spoofing.

***

### How It Works

Visitor identification is a two-step process designed for security:

1. **Client-Side Collection**: The Guardian JS Agent collects anonymous browser signals and sends them to our API. It returns a `requestId`.
2. **Server-Side Resolution**: You send this `requestId` to your backend, which then queries the Guardian API to retrieve the authentic `visitorId` and risk analysis.

This architecture prevents client-side tampering—malicious users cannot simply "edit" their visitor ID in the browser to evade detection.

***

### Integration Guide

#### 1. Client-Side: Generate a Request ID

Install the Guardian JS SDK:

```bash
npm install @guardianstack/guardian-js
```

Initialize the agent and generate a request ID when you need to identify a user (e.g., on login, signup, or payment):

```javascript
import { loadAgent } from "@guardianstack/guardian-js";

// 1. Initialize the agent (usually on app load)
const agentPromise = loadAgent({
  siteKey: "YOUR_PUBLIC_SITE_KEY",
});

// 2. Call this function when a user performs an action
async function getIdentificationToken() {
  const agent = await agentPromise;
  
  // Collect signals and send to Guardian API
  const response = await agent.get();
  const result = await response.json();
  
  // Returns a unique requestId for this specific event
  return result.requestId;
}
```

{% hint style="info" %}
**Note**: The client SDK returns a `requestId`, not the `visitorId` itself. You must send this `requestId` to your server to securely retrieve the identification result.
{% endhint %}

#### 2. Server-Side: Retrieve the Visitor ID

On your backend, use the `requestId` received from the client to fetch the full event details. This ensures you are making decisions based on verified data.

We provide a server-side SDK to make this integration easy and type-safe.

**Install**:

```bash
npm install @guardianstack/guardianjs-server
```

**Implementation**:

```typescript
import { createGuardianClient, isTampering, isVPN } from "@guardianstack/guardianjs-server";

// Initialize the client
const guardian = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY,
});

// Your backend API handler (e.g., /api/login)
app.post('/api/login', async (req, res) => {
  const { username, password, requestId } = req.body;

  try {
    // 1. Verify the event with GuardianStack
    const event = await guardian.getEvent(requestId);

    // 2. Access the stable visitorId
    const { visitorId } = event.identification;
    console.log(`User ${username} has Visitor ID: ${visitorId}`);

    // 3. (Optional) Check for risks
    if (isTampering(event) || isVPN(event)) {
      return res.status(403).json({ error: 'High risk login attempt blocked' });
    }

    // Example: Check if this visitorId is associated with too many accounts
    const accountCount = await db.users.count({ visitorId });
    if (accountCount > 5) {
      return res.status(403).json({ error: 'Too many accounts for this device' });
    }

    // Proceed with login...
    
  } catch (error) {
    console.error("Guardian check failed:", error);
    return res.status(500).json({ error: 'Security check failed' });
  }
});
```

***

### Understanding the Visitor ID

The `visitorId` is a string (e.g., `h7g9s8d7f6g5...`) that uniquely identifies a visitor's device/browser environment.

#### Stability & Accuracy

GuardianStack uses a sophisticated "fuzzy matching" engine that analyzes over 100 distinct signals, including:

* **Hardware Fingerprints**: GPU renderer, audio stack, hardware concurrency, and memory profiles.
* **Browser Environment**: WebGL capabilities, screen properties, and math computation differences.
* **Network Signals**: IP analysis (when combined with device signals) to handle NAT and dynamic IPs.

**Key Features:**

* **Incognito Resistance**: The ID remains stable even if the user switches to Incognito/Private mode.
* **Tamper Resistance**: Advanced detection algorithms identify and flag attempts to spoof browser signals (e.g., anti-detect browsers).
* **Drift Tolerance**: The ID persists through browser updates and minor configuration changes (like zooming or changing window size).

#### Site-Scoped Privacy

Your `visitorId` values are unique to your `siteKey`. A user visiting Site A and Site B (both using GuardianStack) will have two completely different visitor IDs. This ensures cross-site privacy for users while providing accurate identification for your application.

***

### Best Practices

#### Linking to User Data

GuardianStack does not store your user's PII (Personally Identifiable Information). To associate a visitor ID with a user, you should store the link in your own database.

**Recommended Schema:**

| Column         | Type      | Description                      |
| -------------- | --------- | -------------------------------- |
| `user_id`      | UUID      | Your internal user ID            |
| `visitor_id`   | String    | The GuardianStack visitorId      |
| `last_seen_at` | Timestamp | When this link was last verified |

**Use Cases:**

* **Account Takeover**: If a user logs in with a valid password but a *new* `visitorId`, trigger Multi-Factor Authentication (MFA).
* **Multi-Accounting**: Query your DB to find all users sharing the same `visitorId`.
* **Ban Evasion**: If you ban a `user_id`, also blacklist their `visitorId` to prevent them from creating new accounts.

#### Caching

Do not cache the `visitorId` on the client (e.g., in LocalStorage). Always request a fresh `requestId` for critical actions (Login, Signup, Checkout) to ensure you receive the most up-to-date risk analysis and fraud signals.


# Request ID Security

Protecting your fraud detection from replay attacks

Request IDs are temporary tokens that carry fraud assessment data. If attackers steal and reuse them, they can completely bypass your fraud detection.

***

### How Request ID Attacks Work

<figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FZExvfHFW2LXQczjFcK9O%2Fdiagram-export-10-10-2025-17_49_43.svg?alt=media&amp;token=4e499266-a941-44c7-ab9d-727184061e2e" alt=""><figcaption></figcaption></figure>

**The Problem**: Your server sees a valid request ID with good fraud scores, but it's being used by an attacker.

{% hint style="warning" %}
💡 Want early access to Guardian? We're currently onboarding select partners for our closed beta program. [Request Beta Access →](mailto:piero.bassa@mugshotlabs.co)
{% endhint %}

***

### Essential Security Measures

#### 1. Request ID Expiration

```javascript
// Check if request ID is too old
const eventTime = new Date(event.identification.timestamp);
const ageHours = (Date.now() - eventTime.getTime()) / (1000 * 60 * 60);

if (ageHours > 24) {
  return res.status(400).json({ error: 'Fraud check expired' });
}
```

#### 2. Usage Frequency Tracking

```javascript
// Track how many times request ID has been used
const usageCount = await getRequestIdUsage(requestId);

if (usageCount > 3) {
  return res.status(429).json({ error: 'Request ID overused' });
}
```

#### 3. Action-Specific Limits

**Risk-based expiration times:**

* **Profile updates**: 24 hours (low risk)
* **Account changes**: 6 hours (medium risk)
* **Payments**: 1 hour (high risk)
* **Password changes**: 15 minutes (critical)

{% hint style="danger" %}
**Never cache request IDs for more than 24 hours.** \
\
Request IDs contain time-sensitive fraud assessments that lose accuracy over time. Longer caching periods create security vulnerabilities and reduce fraud detection effectiveness.
{% endhint %}

***

### Real-World Impact

#### Attack Examples

* **E-commerce**: Fraudster uses intercepted request ID to validate stolen credit card purchases
* **Banking**: Attacker replays request ID to authorize unauthorized transfers
* **SaaS**: Bulk account creation using harvested "legitimate" fraud assessments

#### Detection Patterns

Watch for these attack indicators:

* Same request ID used from multiple IP addresses
* High frequency of expired request ID attempts
* Geographic inconsistencies (ID generated in US, used in Russia)
* Burst patterns of request ID usage

***

### Implementation Recommendations

#### Security Levels by Action

| Action Type         | Max Age    | Max Usage | Why                                |
| ------------------- | ---------- | --------- | ---------------------------------- |
| **View Profile**    | 24 hours   | 10 uses   | Low risk, user convenience         |
| **Update Account**  | 6 hours    | 3 uses    | Medium risk, reasonable reuse      |
| **Process Payment** | 1 hour     | 2 uses    | High risk, fresh validation needed |
| **Change Password** | 15 minutes | 1 use     | Critical, single-use only          |

#### Response Strategy

* **Low violations**: Log and monitor patterns
* **Medium violations**: Require fresh fraud check
* **High violations**: Block transaction and alert security team
* **Critical violations**: Consider temporary IP restrictions

***

### Key Takeaways

**Request IDs are security tokens** - treat them like temporary API keys:

* ✅ Set expiration based on action risk level
* ✅ Limit usage frequency to prevent replay attacks
* ✅ Monitor patterns and alert on suspicious activity
* ❌ Don't ignore age or usage validation

**Balance security with user experience** - start strict and adjust based on user feedback and attack patterns.

***

**Questions about implementing request ID security for your risk tolerance?** Our team helps customers find the right balance between protection and user experience.


# New Account Fraud Prevention

Detect and block users creating multiple accounts to exploit your platform. Learn how to use device fingerprinting to connect fake accounts to the same device.

### The Problem

Every business with a signup flow faces the same challenge: fraudsters create multiple fake accounts to abuse your system. Whether it's claiming free trials repeatedly, gaming referral programs, or building networks for spam and scam operations, fake account creation costs businesses billions annually.

Traditional defences fail because:

* **Email verification** is trivial to bypass with disposable email services
* **Phone verification** can be defeated with virtual phone numbers
* **IP blocking** fails against VPNs and rotating proxies
* **CAPTCHA** is easily solved by bot farms and AI services
* **Rate limiting by IP** misses distributed attacks

{% hint style="danger" %}
**The result:** A single fraudster can create hundreds of accounts, each appearing legitimate in isolation.
{% endhint %}

#### Common Attack Vectors

| Attack Type               | Description                                                 | Business Impact              |
| ------------------------- | ----------------------------------------------------------- | ---------------------------- |
| **Free Trial Abuse**      | Creating new accounts to extend free trials indefinitely    | Lost revenue, skewed metrics |
| **Promo/Coupon Stacking** | Using multiple accounts to claim one-time offers repeatedly | Direct financial loss        |
| **Referral Fraud**        | Self-referring between fake accounts to earn bonuses        | Inflated CAC, program abuse  |
| **Review Manipulation**   | Fake accounts posting fraudulent reviews                    | Damaged trust, legal risk    |
| **Bonus Abuse**           | Exploiting sign-up bonuses across multiple accounts         | Direct financial loss        |
| **Content Spam**          | Automated account creation for spam distribution            | Platform degradation         |

### The Solution: Device-Based Identity

Guardian Stack links accounts to **devices, not just credentials**. Even when a fraudster uses a new email, phone number, VPN, and incognito mode, they're still using the same physical device — and Guardian detects it.

#### How It Works

<div data-with-frame="true"><figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FXBwaR9F1iPE9aiNYw3au%2FGuardian-Powered%20Registration%20Flow%20-%20visual%20selection%20(1).png?alt=media&amp;token=1841f494-5177-4afa-b17f-d70e6bd8fa62" alt=""><figcaption></figcaption></figure></div>

1. User visits signup page
2. Guardian SDK silently collects device signals
3. User submits registration form
4. Your backend fetches the Guardian event and checks:
   1. Is this device linked to existing accounts?
   2. Is this a bot or automated browser?
   3. Is the user hiding behind a VPN/proxy?
5. Allow, challenge, or deny registration

{% hint style="info" %}
**Key insight:** The `visitorId` persists across incognito sessions, cleared cookies, and browser restarts. It's cryptographically tied to the physical device.
{% endhint %}

***

### Implementation Guide

#### Step 1: Frontend — Capture Device Signals

Install the Guardian JS SDK:

```bash
npm install @guardianstack/guardian-js
```

Initialize Guardian when your app loads, then call `.get()` during registration:

```typescript
import { loadAgent } from "@guardianstack/guardian-js";

// Initialize once when your app starts
const guardian = await loadAgent({
  siteKey: "YOUR_SITE_KEY",
});

async function handleSignup(formData: SignupFormData) {
  // 1. Get Guardian signals before submitting
  const response = await guardian.get();
  const requestId = response?.requestId;

  // 2. Submit registration with Guardian requestId
  const result = await fetch("/api/signup", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      ...formData,
      guardianRequestId: requestId,
    }),
  });

  return result.json();
}
```

#### Step 2: Backend — Verify & Decide

Install the Guardian Server SDK:

```bash
npm install @guardianstack/guardianjs-server
```

Create your signup endpoint with fraud checks:

```typescript
import {
  createGuardianClient,
  isBot,
  isVPN,
  isTampering,
  isIncognito,
  isVirtualized,
} from "@guardianstack/guardianjs-server";

const guardianClient = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY!,
});

app.post("/api/signup", async (req, res) => {
  const { email, password, guardianRequestId } = req.body;

  // 1. Fetch the Guardian event
  const event = await guardianClient.getEvent(guardianRequestId);

  // 2. Extract key identifiers
  const visitorId = event.identification.visitorId;
  const ipAddress = event.identification.ip;

  // 3. Check for automation/bot signals
  if (isBot(event)) {
    return res.status(403).json({
      error: "Registration blocked",
      reason: "Automated access detected",
    });
  }

  // 4. Check for browser tampering (anti-detect browsers)
  if (isTampering(event)) {
    return res.status(403).json({
      error: "Registration blocked",
      reason: "Browser integrity check failed",
    });
  }

  // 5. Check if this device has created accounts before
  const existingAccounts = await db.accounts.findMany({
    where: { visitorId },
  });

  if (existingAccounts.length > 0) {
    return res.status(403).json({
      error: "Registration blocked",
      reason: "You've already created an account",
    });
  }

  // 6. Check velocity (too many attempts from this device)
  const velocity = event.velocity;
  if (velocity["24h"] > 10) {
    return res.status(429).json({
      error: "Too many attempts",
      reason: "Please try again later",
    });
  }

  // 7. All checks passed — create the account
  const newUser = await db.accounts.create({
    data: {
      email,
      password: await hashPassword(password),
      visitorId, // Store for future fraud detection
      signupIp: ipAddress,
      createdAt: new Date(),
    },
  });

  return res.status(201).json({ success: true, userId: newUser.id });
});
```

***

### Real-World Examples

#### Free Trial Abuse Prevention

**Scenario:** Your SaaS offers a 15-day free trial. Fraudsters create new accounts every 15 days to avoid paying.

```typescript
app.post("/api/start-trial", async (req, res) => {
  const { guardianRequestId, email } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check if this device has EVER started a trial
  const previousTrials = await db.trials.findMany({
    where: { visitorId },
  });

  if (previousTrials.length > 0) {
    const lastTrial = previousTrials[0];
    return res.status(403).json({
      error: "Trial unavailable",
      message: "You've already used a free trial on this device",
      previousEmail: maskEmail(lastTrial.email), // "j***@example.com"
    });
  }

  // Create the trial and link to device
  await db.trials.create({
    data: {
      email,
      visitorId,
      startedAt: new Date(),
      expiresAt: addDays(new Date(), 15),
    },
  });

  return res.json({ success: true, trialDays: 15 });
});
```

#### Referral Program Protection

**Scenario:** You offer $20 for each referred user. Fraudsters refer themselves using multiple accounts.

```typescript
app.post("/api/apply-referral", async (req, res) => {
  const { referralCode, guardianRequestId } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const newUserVisitorId = event.identification.visitorId;

  // Find the referrer
  const referrer = await db.users.findUnique({
    where: { referralCode },
  });

  if (!referrer) {
    return res.status(404).json({ error: "Invalid referral code" });
  }

  // Check if referrer and new user share the same device
  if (referrer.visitorId === newUserVisitorId) {
    // Log for fraud review but don't reveal detection
    await logFraudAttempt("self_referral", {
      referrerId: referrer.id,
      visitorId: newUserVisitorId,
    });

    return res.status(400).json({
      error: "Referral not applied",
      message: "This referral code cannot be used",
    });
  }

  // Check if this device has been referred before
  const previousReferrals = await db.referrals.findMany({
    where: { referredVisitorId: newUserVisitorId },
  });

  if (previousReferrals.length > 0) {
    return res.status(400).json({
      error: "Referral not applied",
      message: "This device has already been referred",
    });
  }

  // Valid referral — credit the referrer
  await db.referrals.create({
    data: {
      referrerId: referrer.id,
      referredVisitorId: newUserVisitorId,
      bonusAmount: 20,
      status: "pending",
    },
  });

  return res.json({ success: true, message: "Referral bonus applied!" });
});
```

#### Coupon/Promo Code Abuse Prevention

**Scenario:** A one-time 50% discount code is being reused across fake accounts.

```typescript
app.post("/api/apply-promo", async (req, res) => {
  const { promoCode, guardianRequestId } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  const promo = await db.promoCodes.findUnique({
    where: { code: promoCode },
  });

  if (!promo || !promo.isActive) {
    return res.status(404).json({ error: "Invalid promo code" });
  }

  // Check if promo is one-per-device
  if (promo.onePerDevice) {
    const usedBefore = await db.promoUsage.findFirst({
      where: {
        promoId: promo.id,
        visitorId,
      },
    });

    if (usedBefore) {
      return res.status(400).json({
        error: "Promo code already used",
        message: "This offer is limited to one per device",
      });
    }
  }

  // Record usage and apply discount
  await db.promoUsage.create({
    data: {
      promoId: promo.id,
      visitorId,
      usedAt: new Date(),
    },
  });

  return res.json({
    success: true,
    discount: promo.discountPercent,
  });
});
```

***

### Database Schema Example

Store Guardian identifiers for long-term fraud detection:

```sql
-- Users table with fraud prevention fields
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,

  -- Guardian fraud prevention
  visitor_id VARCHAR(255),          -- Persistent device ID
  signup_ip INET,                   -- IP at registration
  signup_risk_score INTEGER,        -- Trust score at signup

  -- Metadata
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Index for quick device lookups
CREATE INDEX idx_users_visitor_id ON users(visitor_id);

-- Track all visitor IDs seen for a user (device history)
CREATE TABLE user_devices (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id),
  visitor_id VARCHAR(255) NOT NULL,
  first_seen_at TIMESTAMP DEFAULT NOW(),
  last_seen_at TIMESTAMP DEFAULT NOW(),
  is_trusted BOOLEAN DEFAULT false,

  UNIQUE(user_id, visitor_id)
);

-- Fraud events for review
CREATE TABLE fraud_events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  event_type VARCHAR(50) NOT NULL,  -- 'duplicate_device', 'bot_detected', etc.
  visitor_id VARCHAR(255),
  ip_address INET,
  request_data JSONB,
  guardian_event JSONB,             -- Store full event for analysis
  created_at TIMESTAMP DEFAULT NOW()
);
```

***

### Best Practices

#### Do

* **Store `visitorId`** with every account for future cross-referencing
* **Log fraud attempts** without revealing detection methods to users
* **Use risk scoring** for graduated responses instead of hard blocks
* **Combine signals** — a VPN alone isn't fraud, but VPN + incognito + high velocity is suspicious
* **Review edge cases** — legitimate users sometimes trigger signals

#### Don't

* **Don't block VPN users outright** — many legitimate users use VPNs for privacy
* **Don't reveal detection methods** in error messages (avoid "Bot detected")
* **Don't rely solely on IP** — it's easily changed
* **Don't ignore velocity** — rapid signups from one device indicate automation

***

### Testing Your Implementation

Use these scenarios to verify your fraud detection:

| Test Case                        | Expected Behavior                     |
| -------------------------------- | ------------------------------------- |
| Normal signup                    | Account created successfully          |
| Same device, new email           | Blocked ("Already have an account")   |
| Incognito mode signup            | Slight score reduction, still allowed |
| VPN + incognito + rapid attempts | Challenge or block                    |
| Automated browser (Puppeteer)    | Blocked (bot detected)                |
| Anti-detect browser              | Blocked (tampering detected)          |

***

### Conclusion

New account fraud is a persistent threat that traditional verification methods can't solve. By linking accounts to physical devices through Guardian Stack's `visitorId`, you can:

* **Stop serial abusers** who create multiple accounts
* **Protect promotional offers** from exploitation
* **Preserve referral program integrity**
* **Reduce manual fraud review** with automated detection
* **Maintain good user experience** for legitimate customers

The key insight: fraudsters can change emails, phone numbers, and IP addresses easily — but they can't easily change their physical device. Guardian Stack makes device identity the foundation of your fraud prevention strategy.

***

{% hint style="success" %}
**Get Started:** [Sign up for Guardian Stack](https://dashboard.guardianstack.ai/) and get your API keys to implement this today.
{% endhint %}


# Payment Fraud Prevention

Stop fraudulent orders and chargebacks by linking payment attempts to devices. Identify returning fraudsters instantly, even when they use new cards, emails, or identities.

### The Problem

Payment fraud costs businesses over $40 billion annually. Traditional defenses focus on validating the card itself — CVV checks, AVS matching, 3D Secure — but they miss a critical dimension: **who is using the card**.

A fraudster with stolen card details can:

* Pass all card verification checks (they have the full card data)
* Use a different email address each time
* Ship to new addresses or use package forwarding
* Clear cookies and use incognito mode
* Rotate through VPNs and proxies

{% hint style="danger" %}
**The result:** The same fraudster hits your checkout repeatedly with different stolen cards, and each transaction looks like a unique, legitimate customer.
{% endhint %}

#### Common Payment Fraud Patterns

| Fraud Type            | Description                                              | Business Impact                  |
| --------------------- | -------------------------------------------------------- | -------------------------------- |
| **Card Testing**      | Bots test thousands of stolen cards with small purchases | Chargebacks, processor penalties |
| **Stolen Card Fraud** | Using compromised card details for purchases             | Direct loss + chargeback fees    |
| **Friendly Fraud**    | Legitimate purchases disputed as "unauthorized"          | Revenue loss, increased disputes |
| **Account Takeover**  | Accessing accounts to use saved payment methods          | Customer trust damage            |
| **Reseller Fraud**    | Bulk purchases with stolen cards for resale              | Inventory loss, chargebacks      |
| **Refund Abuse**      | Claiming items not received or damaged                   | Direct financial loss            |

#### Why Traditional Fraud Detection Fails

<div data-with-frame="true"><figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FpCkuDRDzhCmLisVFINZN%2FGuardian-Powered%20Registration%20Flow%20-%20visual%20selection%20(3).png?alt=media&amp;token=6825a64e-5332-444d-a4dc-27b5efc6b78b" alt=""><figcaption></figcaption></figure></div>

1. Fraudster uses stolen card #1 → Blocked by bank
2. Same fraudster tries card #2 with new email → Approved (looks like a new customer)
3. Same fraudster tries card #3 with VPN → Approved (different IP address)
4. Same fraudster tries card #4 in incognito → Approved (no cookies to track)

{% hint style="danger" %}
The problem: Each attempt looks like a different person. Your system can't connect them.
{% endhint %}

***

### The Solution: Device-Linked Payment Intelligence

Guardian Stack adds a persistent identity layer to every transaction. Even when fraudsters change cards, emails, addresses, and IP addresses, they're still using the same physical device — and Guardian detects it.

#### How It Works

<div data-with-frame="true"><figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2F3f81ijUlVGW1nAtvIBbX%2FGuardian-Powered%20Registration%20Flow%20-%20visual%20selection%20(4).png?alt=media&amp;token=190a387a-ff0e-4f6d-a8dc-55427d3350c8" alt=""><figcaption></figcaption></figure></div>

1. Customer reaches checkout
2. Guardian SDK silently collects device signals
3. Customer submits payment
4. Your backend fetches the Guardian event and checks:
   * Has this device had chargebacks before?
   * Is this device testing multiple cards?
   * Does the location match the billing address?
   * Is this a bot or automated browser?
5. Approve, review, or decline the transaction

{% hint style="info" %}
**Key insight:** The `visitorId` persists across sessions, browsers, and cleared cookies. A fraudster who caused chargebacks last month is instantly recognizable today.
{% endhint %}

***

### Implementation Guide

#### Step 1: Frontend — Capture Device Signals at Checkout

Install the Guardian JS SDK:

```bash
npm install @guardianstack/guardian-js
```

Initialize Guardian when your app loads, then call `.get()` at checkout:

```typescript
import { loadAgent } from "@guardianstack/guardian-js";

// Initialize once when your app starts
const guardian = await loadAgent({
  siteKey: "YOUR_SITE_KEY",
});

async function handleCheckout(orderData: OrderData) {
  // 1. Get Guardian signals before processing payment
  const response = await guardian.get();
  const requestId = response?.requestId;

  // 2. Submit order with Guardian requestId
  const result = await fetch("/api/checkout", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      ...orderData,
      guardianRequestId: requestId,
    }),
  });

  return result.json();
}
```

#### Step 2: Backend — Assess Risk Before Charging

Install the Guardian Server SDK:

```bash
npm install @guardianstack/guardianjs-server
```

Create your checkout endpoint with fraud checks:

```typescript
import {
  createGuardianClient,
  isBot,
  isVPN,
  isTampering,
  isIncognito,
  isVirtualized,
} from "@guardianstack/guardianjs-server";

const guardianClient = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY!,
});

app.post("/api/checkout", async (req, res) => {
  const { guardianRequestId, cardToken, amount, shippingAddress } = req.body;

  // 1. Fetch the Guardian event
  const event = await guardianClient.getEvent(guardianRequestId);

  // 2. Extract key identifiers
  const visitorId = event.identification.visitorId;
  const ipAddress = event.identification.ip;
  const deviceLocation = event.identification.location;

  // 3. Check for automation (card testing bots)
  if (isBot(event)) {
    await logFraudAttempt("bot_detected", { visitorId, amount });
    return res.status(403).json({
      error: "Transaction declined",
      code: "SECURITY_CHECK_FAILED",
    });
  }

  // 4. Check for browser tampering (anti-detect browsers)
  if (isTampering(event)) {
    await logFraudAttempt("tampering_detected", { visitorId, amount });
    return res.status(403).json({
      error: "Transaction declined",
      code: "SECURITY_CHECK_FAILED",
    });
  }

  // 5. Check if this device has previous chargebacks
  const previousChargebacks = await db.chargebacks.count({
    where: { visitorId },
  });

  if (previousChargebacks > 0) {
    await logFraudAttempt("previous_chargeback", { visitorId, amount });
    return res.status(403).json({
      error: "Transaction declined",
      code: "PAYMENT_NOT_ACCEPTED",
    });
  }

  // 6. Check velocity (card testing detection)
  const velocity = event.velocity;
  if (velocity["5m"] > 3 || velocity["1h"] > 10) {
    await logFraudAttempt("high_velocity", { visitorId, velocity, amount });
    return res.status(429).json({
      error: "Too many payment attempts",
      code: "RATE_LIMITED",
    });
  }

  // 7. Check for geographic anomalies
  const ipCountry = deviceLocation?.country_code;
  const cardCountry = await getCardCountry(cardToken); // From your payment processor

  if (ipCountry && cardCountry && ipCountry !== cardCountry && isVPN(event)) {
    // VPN + country mismatch = high risk
    await flagForManualReview(req.body, event, "geo_mismatch_vpn");
    // Optionally still process but flag for review
  }

  // 8. All checks passed — process payment
  const paymentResult = await processPayment(cardToken, amount);

  if (paymentResult.success) {
    // Store visitorId with order for future fraud correlation
    await db.orders.create({
      data: {
        orderId: paymentResult.orderId,
        visitorId,
        ipAddress,
        amount,
        deviceCountry: ipCountry,
      },
    });
  }

  return res.json(paymentResult);
});
```

***

### Real-World Examples

#### Stolen Card Detection

**Scenario:** A fraudster obtains stolen card details and attempts purchases on your site.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check how many DIFFERENT cards this device has used
  const uniqueCards = await db.orders.findMany({
    where: { visitorId },
    select: { cardLastFour: true, cardBrand: true },
    distinct: ["cardLastFour", "cardBrand"],
  });

  // Multiple cards from same device = card testing
  if (uniqueCards.length >= 3) {
    await logFraudAttempt("multiple_cards_same_device", {
      visitorId,
      cardCount: uniqueCards.length,
    });

    return res.status(403).json({
      error: "Transaction declined",
      message: "Please contact support",
    });
  }

  // Check if any previous orders from this device were charged back
  const chargedBackOrders = await db.orders.findMany({
    where: {
      visitorId,
      status: "chargeback",
    },
  });

  if (chargedBackOrders.length > 0) {
    return res.status(403).json({
      error: "Transaction declined",
      code: "STOLEN_CARD_DETECTED",
    });
  }

  // Process payment...
});
```

#### Card Testing Prevention

**Scenario:** Bots test thousands of stolen cards with small purchases to find valid ones.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;
  const velocity = event.velocity;

  // High velocity = card testing
  if (velocity["5m"] > 5) {
    // Block and alert
    await alertSecurityTeam("card_testing_detected", {
      visitorId,
      attemptsIn5Min: velocity["5m"],
      ipAddress: event.identification.ip,
    });

    return res.status(429).json({
      error: "Too many attempts",
      retryAfter: 3600, // 1 hour
    });
  }

  // Check for bot indicators
  if (isBot(event) || isVirtualized(event)) {
    return res.status(403).json({
      error: "Transaction declined",
    });
  }

  // Check decline rate for this device
  const recentAttempts = await db.paymentAttempts.findMany({
    where: {
      visitorId,
      createdAt: { gte: subHours(new Date(), 1) },
    },
  });

  const declinedCount = recentAttempts.filter((a) => a.status === "declined").length;
  const declineRate = recentAttempts.length > 0 
    ? declinedCount / recentAttempts.length 
    : 0;

  if (declineRate > 0.5 && recentAttempts.length >= 3) {
    return res.status(403).json({
      error: "Transaction declined",
      code: "HIGH_DECLINE_RATE",
    });
  }

  // Process payment...
});
```

#### Returning Fraudster Detection

**Scenario:** A fraudster who caused chargebacks 3 months ago returns with a new email, card, and VPN.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check against fraud blacklist (populated from chargebacks)
  const isBlacklisted = await db.fraudBlacklist.findFirst({
    where: { visitorId },
  });

  if (isBlacklisted) {
    // Don't reveal why — just decline
    await logFraudAttempt("blacklisted_device_returned", {
      visitorId,
      originalIncident: isBlacklisted.reason,
      daysSinceBlacklist: daysBetween(isBlacklisted.createdAt, new Date()),
    });

    return res.status(403).json({
      error: "Transaction declined",
      code: "PAYMENT_NOT_ACCEPTED",
    });
  }

  // Check if this device is linked to ANY account with chargebacks
  const linkedAccounts = await db.accounts.findMany({
    where: { visitorId },
    include: { orders: { where: { status: "chargeback" } } },
  });

  const hasChargebackHistory = linkedAccounts.some(
    (account) => account.orders.length > 0
  );

  if (hasChargebackHistory) {
    return res.status(403).json({
      error: "Transaction declined",
    });
  }

  // Process payment...
});
```

#### Geographic Anomaly Detection

**Scenario:** Card billing address is in New York, but the device is connecting from Eastern Europe via VPN.

```typescript
app.post("/api/checkout", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const { billingAddress, shippingAddress } = req.body;

  const deviceLocation = event.identification.location;
  const ipInfo = event.ipInfo;

  // Detect VPN with timezone mismatch
  const vpnData = event.vpn;
  if (vpnData?.detected && vpnData?.timezoneDifference > 3) {
    // VPN detected AND significant timezone mismatch
    await flagForManualReview(req.body, event, "vpn_timezone_mismatch");
  }

  // Check if IP is from datacenter (not residential)
  if (ipInfo?.is_datacenter && !ipInfo?.is_mobile) {
    // Datacenter IP + not mobile = likely proxy/VPN
    await flagForManualReview(req.body, event, "datacenter_ip");
  }

  // Compare device country with billing country
  const deviceCountry = deviceLocation?.country_code;
  const billingCountry = billingAddress?.country;

  if (deviceCountry && billingCountry && deviceCountry !== billingCountry) {
    // Country mismatch — increase scrutiny
    if (isVPN(event) || isIncognito(event)) {
      // High risk: country mismatch + privacy tools
      return res.status(403).json({
        error: "Transaction declined",
        code: "VERIFICATION_REQUIRED",
      });
    }

    // Medium risk: just flag for review
    await flagForManualReview(req.body, event, "country_mismatch");
  }

  // Process payment...
});
```

***

### Handling Chargebacks: Close the Loop

When you receive a chargeback, update your fraud database to catch the same fraudster next time:

```typescript
// Webhook handler for chargeback notifications
app.post("/webhooks/chargeback", async (req, res) => {
  const { orderId, reason, amount } = req.body;

  // Get the original order with device info
  const order = await db.orders.findUnique({
    where: { id: orderId },
  });

  if (!order?.visitorId) {
    return res.json({ received: true });
  }

  // Update order status
  await db.orders.update({
    where: { id: orderId },
    data: { status: "chargeback" },
  });

  // Add device to fraud blacklist
  await db.fraudBlacklist.upsert({
    where: { visitorId: order.visitorId },
    create: {
      visitorId: order.visitorId,
      reason: "chargeback",
      originalOrderId: orderId,
      chargebackAmount: amount,
    },
    update: {
      chargebackCount: { increment: 1 },
      totalChargebackAmount: { increment: amount },
      lastChargebackAt: new Date(),
    },
  });

  // Alert fraud team for pattern analysis
  await alertFraudTeam("chargeback_received", {
    visitorId: order.visitorId,
    orderId,
    amount,
    reason,
    customerEmail: order.customerEmail,
    ipAddress: order.ipAddress,
  });

  return res.json({ received: true });
});
```

***

### Database Schema Example

```sql
-- Orders with fraud prevention data
CREATE TABLE orders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id UUID REFERENCES customers(id),
  amount DECIMAL(10, 2) NOT NULL,
  status VARCHAR(50) DEFAULT 'pending',
  
  -- Guardian fraud prevention
  visitor_id VARCHAR(255),
  ip_address INET,
  device_country VARCHAR(2),
  risk_score INTEGER,
  risk_reasons TEXT[],
  
  -- Card info (tokenized)
  card_last_four VARCHAR(4),
  card_brand VARCHAR(20),
  billing_country VARCHAR(2),
  
  created_at TIMESTAMP DEFAULT NOW()
);

-- Index for fraud lookups
CREATE INDEX idx_orders_visitor_id ON orders(visitor_id);
CREATE INDEX idx_orders_status ON orders(status);

-- Fraud blacklist
CREATE TABLE fraud_blacklist (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255) UNIQUE NOT NULL,
  reason VARCHAR(100) NOT NULL,
  chargeback_count INTEGER DEFAULT 1,
  total_chargeback_amount DECIMAL(10, 2) DEFAULT 0,
  original_order_id UUID,
  last_chargeback_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Payment attempts (for velocity tracking)
CREATE TABLE payment_attempts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255),
  status VARCHAR(50),  -- 'approved', 'declined', 'error'
  decline_reason VARCHAR(100),
  amount DECIMAL(10, 2),
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_payment_attempts_visitor_time 
  ON payment_attempts(visitor_id, created_at);
```

***

### Best Practices

#### Do

* **Store `visitorId`** with every order for chargeback correlation
* **Link chargebacks** back to device IDs to catch returning fraudsters
* **Use risk scoring** for graduated responses (approve/review/decline)
* **Monitor velocity** to catch card testing attacks
* **Flag for review** rather than auto-decline borderline cases
* **Track decline rates** per device to identify card testers

#### Don't

* **Don't reveal fraud detection logic** in error messages
* **Don't block VPNs outright** — many legitimate customers use them
* **Don't rely solely on AVS/CVV** — fraudsters often have full card data
* **Don't ignore small transactions** — they may be card testing probes
* **Don't delete fraud data** — historical patterns are valuable

***

### Key Metrics to Track

| Metric                          | Description                           | Target         |
| ------------------------------- | ------------------------------------- | -------------- |
| **Chargeback Rate**             | Chargebacks / Total Transactions      | < 0.5%         |
| **Fraud Detection Rate**        | Blocked fraudulent / Total fraudulent | > 90%          |
| **False Positive Rate**         | Legitimate blocked / Total blocked    | < 5%           |
| **Card Testing Blocks**         | Velocity-based blocks per day         | Monitor trends |
| **Returning Fraudster Catches** | Blacklisted devices blocked           | Track monthly  |

***

### Conclusion

Payment fraud is a continuous battle, but you don't have to fight blind. By linking every transaction to a persistent device identity, Guardian Stack lets you:

* **Catch returning fraudsters** even with new cards and identities
* **Stop card testing bots** before they find valid cards
* **Reduce chargebacks** by declining high-risk transactions
* **Identify fraud rings** by connecting related devices
* **Build institutional memory** that improves over time

The key insight: fraudsters can steal unlimited cards, but they have limited devices. Make the device your anchor point for fraud prevention.

***

{% hint style="success" %}
**Get Started:** [Sign up for Guardian Stack](https://dashboard.guardianstack.ai/) and protect your checkout today.
{% endhint %}


# Account Takeover Prevention

Stop unauthorized access while speeding up logins for recognized users. Adapt authentication requirements in real-time based on device recognition and risk signals - require MFA only when it matters.

### The Problem

Account takeover (ATO) is one of the most damaging forms of fraud. Attackers gain access to legitimate user accounts through stolen credentials, phishing, or credential stuffing - then drain funds, steal data, or make fraudulent purchases.

The challenge is twofold:

1. **Attackers have valid credentials** - Username and password checks pass
2. **Legitimate users hate friction** - Too much MFA drives customers away

Traditional approaches force a choice: either frustrate every user with constant verification, or leave accounts vulnerable to takeover.

#### Common Attack Vectors

| Attack Type             | Description                                                   | Scale                        |
| ----------------------- | ------------------------------------------------------------- | ---------------------------- |
| **Credential Stuffing** | Automated login attempts using leaked username/password pairs | Millions of attempts per day |
| **Phishing**            | Tricking users into revealing credentials                     | Targeted attacks             |
| **Session Hijacking**   | Stealing active session tokens                                | Individual accounts          |
| **SIM Swapping**        | Taking over phone numbers to bypass SMS MFA                   | High-value targets           |
| **Brute Force**         | Guessing passwords through repeated attempts                  | Automated attacks            |
| **Password Spraying**   | Trying common passwords across many accounts                  | Enterprise targets           |

#### The Core Problem

1. Attacker has stolen credentials
2. Enters correct username + password
3. Traditional system says "Credentials valid" → Access granted

{% hint style="warning" %}
**The credentials are correct.** How do you know it's not the real user?
{% endhint %}

***

### The Solution: Device-Based Recognition

Guardian Stack recognizes **the device**, not just the credentials. When the account owner logs in from their usual device, they sail through. When an attacker logs in with stolen credentials from a different device, additional verification is triggered.

**How It Works**

<figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FTz3A9YMRzUhzycKULPKK%2FGuardian-Powered%20Registration%20Flow%20-%20visual%20selection%20(5).png?alt=media&amp;token=a0f70802-6ecd-4fee-8f8f-fd5b0f275f43" alt=""><figcaption></figcaption></figure>

1. User enters login credentials
2. Guardian SDK silently collects device signals
3. Your backend fetches the Guardian event and checks:
   * Is this a device the user has logged in from before?
   * Is this a bot or automated browser?
   * Is the user hiding behind a VPN/proxy?
   * Does the location match the user's history?
4. Based on risk level → Allow, challenge with MFA, or block

**The Result**

* **Recognized device:** Instant login, no friction
* **New device:** Require email/SMS verification
* **Suspicious device:** Require strong MFA or block
* **Bot/attacker:** Block immediately

***

### Implementation Guide

#### Step 1: Frontend - Capture Device Signals at Login

Install the Guardian JS SDK:

```bash
npm install @guardianstack/guardian-js
```

Initialize Guardian and call `.get()` during login:

```typescript
import { loadAgent } from "@guardianstack/guardian-js";

// Initialize once when your app starts
const guardian = await loadAgent({
  siteKey: "YOUR_SITE_KEY",
});

async function handleLogin(email: string, password: string) {
  // 1. Get Guardian signals
  const response = await guardian.get();
  const requestId = response?.requestId;

  // 2. Submit login with Guardian requestId
  const result = await fetch("/api/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      email,
      password,
      guardianRequestId: requestId,
    }),
  });

  return result.json();
}
```

#### Step 2: Backend - Adaptive Authentication

Install the Guardian Server SDK:

```bash
npm install @guardianstack/guardianjs-server
```

Create your login endpoint with risk-based authentication:

```typescript
import {
  createGuardianClient,
  isBot,
  isVPN,
  isTampering,
  isIncognito,
  isVirtualized,
} from "@guardianstack/guardianjs-server";

const guardianClient = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY!,
});

app.post("/api/login", async (req, res) => {
  const { email, password, guardianRequestId } = req.body;

  // 1. Verify credentials first
  const user = await verifyCredentials(email, password);
  if (!user) {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  // 2. Fetch Guardian event
  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // 3. Check for automation (credential stuffing bots)
  if (isBot(event) || isTampering(event)) {
    await logSecurityEvent("bot_login_attempt", { email, visitorId });
    return res.status(403).json({ error: "Access denied" });
  }

  // 4. Check if this is a recognized device for this user
  const knownDevice = await db.userDevices.findFirst({
    where: {
      userId: user.id,
      visitorId,
      isVerified: true,
    },
  });

  if (knownDevice) {
    // Known device — allow immediate access
    await updateLastSeen(knownDevice.id);
    const token = generateSessionToken(user);
    return res.json({ success: true, token });
  }

  // 5. New device — assess risk level
  const riskLevel = assessLoginRisk(event, user);

  if (riskLevel === "high") {
    // Block suspicious attempts
    await logSecurityEvent("high_risk_login_blocked", { email, visitorId });
    return res.status(403).json({
      error: "Login blocked",
      message: "Please contact support",
    });
  }

  if (riskLevel === "medium") {
    // Require MFA for suspicious logins
    const challengeToken = await createMfaChallenge(user, visitorId);
    return res.status(202).json({
      requiresMfa: true,
      challengeToken,
      methods: ["sms", "email", "totp"],
    });
  }

  // Low risk new device — require light verification
  const challengeToken = await createMfaChallenge(user, visitorId);
  return res.status(202).json({
    requiresMfa: true,
    challengeToken,
    methods: ["email"],
    message: "New device detected. Please verify your identity.",
  });
});
```

#### Step 3: Risk Assessment Function

```typescript
function assessLoginRisk(event: GuardianEvent, user: User): "low" | "medium" | "high" {
  // High-risk signals — block or require strong MFA
  if (isBot(event) || isTampering(event)) {
    return "high";
  }

  if (isVirtualized(event)) {
    return "high";
  }

  // Check velocity (credential stuffing indicator)
  const velocity = event.velocity;
  if (velocity?.["5m"] > 5 || velocity?.["1h"] > 20) {
    return "high";
  }

  // Medium-risk signals — require MFA
  if (isVPN(event) && event.vpn?.timezoneDifference > 4) {
    return "medium";
  }

  if (event.ipInfo?.is_datacenter) {
    return "medium";
  }

  // Geographic anomaly
  const deviceCountry = event.identification.location?.country_code;
  const userCountry = user.lastKnownCountry;
  if (deviceCountry && userCountry && deviceCountry !== userCountry) {
    return "medium";
  }

  // Low-risk signals
  if (isVPN(event) || isIncognito(event)) {
    return "low"; // Still require verification for new device
  }

  return "low";
}
```

#### Step 4: MFA Verification & Device Registration

```typescript
app.post("/api/verify-mfa", async (req, res) => {
  const { challengeToken, code, rememberDevice } = req.body;

  // 1. Verify the MFA code
  const challenge = await getMfaChallenge(challengeToken);
  if (!challenge || !verifyMfaCode(challenge, code)) {
    return res.status(401).json({ error: "Invalid code" });
  }

  const user = await db.users.findUnique({ where: { id: challenge.userId } });

  // 2. If user chose to remember this device, save it
  if (rememberDevice) {
    await db.userDevices.upsert({
      where: {
        userId_visitorId: {
          userId: user.id,
          visitorId: challenge.visitorId,
        },
      },
      create: {
        userId: user.id,
        visitorId: challenge.visitorId,
        isVerified: true,
        deviceName: challenge.deviceInfo?.browser || "Unknown device",
        firstSeenAt: new Date(),
        lastSeenAt: new Date(),
      },
      update: {
        isVerified: true,
        lastSeenAt: new Date(),
      },
    });
  }

  // 3. Complete login
  const token = generateSessionToken(user);
  await clearMfaChallenge(challengeToken);

  return res.json({
    success: true,
    token,
    deviceRemembered: rememberDevice,
  });
});
```

***

### Real-World Examples

#### Credential Stuffing Prevention

**Scenario:** Attackers use bots to test millions of stolen username/password combinations.

```typescript
app.post("/api/login", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check for bot signals
  if (isBot(event)) {
    // Don't reveal detection — just fail silently
    await logSecurityEvent("credential_stuffing_blocked", { visitorId });
    
    // Add artificial delay to slow down attacks
    await sleep(2000);
    
    return res.status(401).json({ error: "Invalid credentials" });
  }

  // Check login velocity for this device
  const velocity = event.velocity;
  if (velocity["5m"] > 10 || velocity["1h"] > 50) {
    await logSecurityEvent("high_velocity_login", { visitorId, velocity });
    
    return res.status(429).json({
      error: "Too many attempts",
      retryAfter: 3600,
    });
  }

  // Check failed login attempts from this device
  const recentFailures = await db.loginAttempts.count({
    where: {
      visitorId,
      success: false,
      createdAt: { gte: subMinutes(new Date(), 15) },
    },
  });

  if (recentFailures >= 5) {
    return res.status(429).json({
      error: "Too many failed attempts",
      retryAfter: 900,
    });
  }

  // Continue with normal login...
});
```

#### Impossible Travel Detection

**Scenario:** User logs in from New York, then 10 minutes later from Tokyo — physically impossible.

```typescript
app.post("/api/login", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const user = await verifyCredentials(req.body.email, req.body.password);
  
  if (!user) {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  const currentLocation = event.identification.location;
  const lastLogin = await db.loginHistory.findFirst({
    where: { userId: user.id },
    orderBy: { createdAt: "desc" },
  });

  if (lastLogin && currentLocation) {
    const timeSinceLastLogin = Date.now() - lastLogin.createdAt.getTime();
    const hoursSinceLastLogin = timeSinceLastLogin / (1000 * 60 * 60);

    const distance = calculateDistance(
      lastLogin.latitude,
      lastLogin.longitude,
      currentLocation.latitude,
      currentLocation.longitude
    );

    // Impossible if distance > 500 miles per hour
    const requiredHours = distance / 500;
    
    if (hoursSinceLastLogin < requiredHours && !isVPN(event)) {
      await logSecurityEvent("impossible_travel", {
        userId: user.id,
        from: lastLogin.city,
        to: currentLocation.city,
      });

      return res.status(202).json({
        requiresMfa: true,
        reason: "unusual_location",
        message: "We noticed a login from a new location. Please verify your identity.",
      });
    }
  }

  // Continue with login...
});
```

#### Session Anomaly Detection

**Scenario:** Active session suddenly changes device fingerprint — possible session hijacking.

```typescript
async function validateSession(req, res, next) {
  const sessionToken = req.headers.authorization?.split(" ")[1];
  const guardianRequestId = req.headers["x-guardian-request-id"];

  const session = await getSession(sessionToken);
  if (!session) {
    return res.status(401).json({ error: "Invalid session" });
  }

  if (guardianRequestId) {
    const event = await guardianClient.getEvent(guardianRequestId);
    const currentVisitorId = event.identification.visitorId;

    if (session.visitorId && session.visitorId !== currentVisitorId) {
      // Device changed mid-session — possible hijacking
      await logSecurityEvent("session_device_mismatch", {
        userId: session.userId,
        originalDevice: session.visitorId,
        currentDevice: currentVisitorId,
      });

      await invalidateSession(sessionToken);
      
      return res.status(401).json({
        error: "Session expired",
        message: "Please log in again",
      });
    }
  }

  req.user = session.user;
  next();
}
```

#### Adaptive MFA for Sensitive Actions

**Scenario:** Normal browsing needs no MFA. Changing password or transferring funds requires verification even on known devices.

```typescript
async function requireElevatedAuth(req, res, next) {
  const guardianRequestId = req.headers["x-guardian-request-id"];
  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check if user recently completed MFA
  const recentMfa = await db.mfaCompletions.findFirst({
    where: {
      userId: req.user.id,
      visitorId,
      createdAt: { gte: subMinutes(new Date(), 10) },
    },
  });

  if (recentMfa) {
    return next();
  }

  const riskLevel = assessLoginRisk(event, req.user);

  if (riskLevel === "high") {
    return res.status(202).json({
      requiresMfa: true,
      methods: ["totp"],
      reason: "sensitive_action",
    });
  }

  return res.status(202).json({
    requiresMfa: true,
    methods: ["totp", "email"],
    reason: "sensitive_action",
  });
}

app.post("/api/change-password", requireElevatedAuth, changePasswordHandler);
app.post("/api/transfer-funds", requireElevatedAuth, transferFundsHandler);
```

***

### Database Schema Example

```sql
-- Track devices per user
CREATE TABLE user_devices (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  visitor_id VARCHAR(255) NOT NULL,
  is_verified BOOLEAN DEFAULT false,
  device_name VARCHAR(255),
  first_seen_at TIMESTAMP DEFAULT NOW(),
  last_seen_at TIMESTAMP DEFAULT NOW(),
  
  UNIQUE(user_id, visitor_id)
);

CREATE INDEX idx_user_devices_lookup 
  ON user_devices(user_id, visitor_id);

-- Login history for anomaly detection
CREATE TABLE login_history (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id),
  visitor_id VARCHAR(255),
  ip_address INET,
  country VARCHAR(2),
  city VARCHAR(100),
  latitude DECIMAL(10, 8),
  longitude DECIMAL(11, 8),
  success BOOLEAN,
  risk_level VARCHAR(20),
  created_at TIMESTAMP DEFAULT NOW()
);

-- MFA challenges
CREATE TABLE mfa_challenges (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id),
  visitor_id VARCHAR(255),
  challenge_token VARCHAR(255) UNIQUE,
  method VARCHAR(50),
  code_hash VARCHAR(255),
  expires_at TIMESTAMP,
  completed_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Security events for monitoring
CREATE TABLE security_events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  event_type VARCHAR(100) NOT NULL,
  user_id UUID,
  visitor_id VARCHAR(255),
  ip_address INET,
  metadata JSONB,
  created_at TIMESTAMP DEFAULT NOW()
);
```

***

### User Experience: Device Management

Let users see and manage their recognized devices:

```typescript
app.get("/api/my-devices", async (req, res) => {
  const devices = await db.userDevices.findMany({
    where: { userId: req.user.id },
    orderBy: { lastSeenAt: "desc" },
    select: {
      id: true,
      deviceName: true,
      isVerified: true,
      firstSeenAt: true,
      lastSeenAt: true,
    },
  });

  return res.json({ devices });
});

app.delete("/api/my-devices/:deviceId", async (req, res) => {
  await db.userDevices.deleteMany({
    where: {
      id: req.params.deviceId,
      userId: req.user.id,
    },
  });

  return res.json({ success: true });
});

app.post("/api/logout-all-devices", requireElevatedAuth, async (req, res) => {
  await db.userDevices.updateMany({
    where: { userId: req.user.id },
    data: { isVerified: false },
  });

  await db.sessions.deleteMany({
    where: { userId: req.user.id },
  });

  return res.json({ success: true });
});
```

***

### Best Practices

#### Do

* **Remember verified devices** to reduce friction for legitimate users
* **Require MFA on new devices** even with correct credentials
* **Use risk-based authentication** — adapt requirements to threat level
* **Log all login attempts** with device fingerprints for forensics
* **Let users manage devices** — view and revoke recognized devices
* **Monitor for anomalies** — impossible travel, device changes, velocity spikes

#### Don't

* **Don't rely solely on passwords** — they're often compromised
* **Don't trust SMS alone** — vulnerable to SIM swapping
* **Don't block VPNs outright** — many legitimate users use them
* **Don't reveal why login failed** — helps attackers refine attacks
* **Don't skip MFA for "trusted" IPs** — IPs are easily spoofed

***

### Conclusion

Account takeover attacks succeed because they have valid credentials. Traditional systems can't distinguish between the real user and an attacker with stolen passwords.

Guardian Stack solves this by adding a device layer to authentication. The result:

* **Recognized users** log in instantly without friction
* **New devices** require verification — even with correct password
* **Suspicious devices** are blocked or challenged with strong MFA
* **Attackers** can't bypass protection just by having credentials

The key insight: Passwords can be stolen. Devices can't be cloned. Make the device part of your authentication.

***

{% hint style="success" %}
**Get Started:** [Sign up for Guardian Stack](https://dashboard.guardianstack.ai/) and protect your users today.
{% endhint %}


# Returning User Experience

Recognize returning visitors instantly — without cookies, logins, or personal data. Create personalized, frictionless experiences that span sessions, devices, and browsers.

### The Problem

Every time a user returns to your site, they start from scratch:

* **E-commerce:** Cart is empty, preferences forgotten, recommendations generic
* **SaaS:** Has to log in again, settings reset, onboarding repeated
* **Content sites:** Sees the same content, no reading history, preferences lost
* **Forms:** Re-enters the same information every time

Traditional solutions have critical limitations:

| Method             | Problem                                                        |
| ------------------ | -------------------------------------------------------------- |
| **Cookies**        | Cleared by users, blocked by browsers, don't survive incognito |
| **Local Storage**  | Same issues as cookies, plus easily wiped                      |
| **Login Required** | Creates friction, many users won't sign up                     |
| **IP Address**     | Changes constantly, shared by many users                       |
| **Email/Phone**    | Requires user to provide personal data upfront                 |

{% hint style="warning" %}
**The result:** You treat every visitor like a stranger, even your most loyal customers.
{% endhint %}

***

### The Solution: Device-Based Recognition

Guardian Stack's `visitorId` persists across sessions, browsers, and cleared cookies. You can recognize returning visitors the moment they land — before they log in, without asking for personal information.

**How It Works**

<div data-with-frame="true"><figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2F3jG9GzDs7q75hhE6bnUq%2FGuardian-Powered%20Registration%20Flow%20-%20visual%20selection%20(6).png?alt=media&amp;token=df0692c3-eda7-4afe-a8b6-bf429761b0b6" alt=""><figcaption></figcaption></figure></div>

1. Visitor lands on your site
2. Guardian SDK silently generates a persistent `visitorId`
3. Your backend checks: Have we seen this `visitorId` before?
4. If yes → Personalize their experience immediately
5. If no → Treat as new visitor, start building their profile

**What You Can Do**

* **Pre-fill forms** with previously entered information
* **Restore cart contents** from abandoned sessions
* **Show relevant recommendations** based on browsing history
* **Skip repeated onboarding** for returning users
* **Streamline authentication** — recognize device, reduce MFA
* **Maintain preferences** — language, currency, theme, settings

***

### Implementation Guide

#### Step 1: Frontend — Identify Visitors on Page Load

Install the Guardian JS SDK:

```bash
npm install @guardianstack/guardian-js
```

Initialize Guardian early and identify the visitor:

```typescript
import { loadAgent } from "@guardianstack/guardian-js";

// Initialize once when your app starts
const guardian = await loadAgent({
  siteKey: "YOUR_SITE_KEY",
});

// Get visitor identity on page load
async function identifyVisitor() {
  const response = await guardian.get();
  const requestId = response?.requestId;

  // Send to your backend to check if returning visitor
  const visitorData = await fetch("/api/identify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ guardianRequestId: requestId }),
  });

  return visitorData.json();
}

// Call on app initialization
const visitor = await identifyVisitor();

if (visitor.isReturning) {
  // Apply personalization
  applyPreferences(visitor.preferences);
  restoreCart(visitor.cart);
  showRelevantContent(visitor.interests);
}
```

#### Step 2: Backend — Recognize and Personalize

Install the Guardian Server SDK:

```bash
npm install @guardianstack/guardianjs-server
```

Create an endpoint to identify visitors and return their profile:

```typescript
import {
  createGuardianClient,
  isBot,
} from "@guardianstack/guardianjs-server";

const guardianClient = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY!,
});

app.post("/api/identify", async (req, res) => {
  const { guardianRequestId } = req.body;

  // 1. Fetch Guardian event
  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // 2. Skip bots — they don't need personalization
  if (isBot(event)) {
    return res.json({ isReturning: false, isBot: true });
  }

  // 3. Check if we've seen this visitor before
  const visitorProfile = await db.visitorProfiles.findUnique({
    where: { visitorId },
    include: {
      preferences: true,
      cartItems: true,
      browsingHistory: true,
    },
  });

  if (!visitorProfile) {
    // New visitor — create profile for future visits
    await db.visitorProfiles.create({
      data: {
        visitorId,
        firstSeenAt: new Date(),
        lastSeenAt: new Date(),
        visitCount: 1,
      },
    });

    return res.json({
      isReturning: false,
      visitorId, // Use for client-side tracking
    });
  }

  // 4. Returning visitor — update and return their data
  await db.visitorProfiles.update({
    where: { visitorId },
    data: {
      lastSeenAt: new Date(),
      visitCount: { increment: 1 },
    },
  });

  return res.json({
    isReturning: true,
    visitCount: visitorProfile.visitCount + 1,
    preferences: visitorProfile.preferences,
    cart: visitorProfile.cartItems,
    interests: deriveInterests(visitorProfile.browsingHistory),
    lastVisit: visitorProfile.lastSeenAt,
  });
});
```

#### Step 3: Save Visitor Activity

Track visitor behavior to personalize future visits:

```typescript
// Save preferences (language, currency, theme, etc.)
app.post("/api/preferences", async (req, res) => {
  const { guardianRequestId, preferences } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  await db.visitorPreferences.upsert({
    where: { visitorId },
    create: {
      visitorId,
      ...preferences,
    },
    update: preferences,
  });

  return res.json({ success: true });
});

// Save cart for abandoned cart recovery
app.post("/api/cart/save", async (req, res) => {
  const { guardianRequestId, items } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Clear existing cart items
  await db.cartItems.deleteMany({ where: { visitorId } });

  // Save current cart
  await db.cartItems.createMany({
    data: items.map((item) => ({
      visitorId,
      productId: item.productId,
      quantity: item.quantity,
      savedAt: new Date(),
    })),
  });

  return res.json({ success: true });
});

// Track browsing for recommendations
app.post("/api/track/view", async (req, res) => {
  const { guardianRequestId, productId, category, duration } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  await db.browsingHistory.create({
    data: {
      visitorId,
      productId,
      category,
      viewDuration: duration,
      viewedAt: new Date(),
    },
  });

  return res.json({ success: true });
});
```

***

### Real-World Examples

#### E-commerce: Abandoned Cart Recovery

**Scenario:** Visitor adds items to cart, leaves, returns 3 days later — cart is waiting for them.

```typescript
// On page load
app.post("/api/identify", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.guardianRequestId);
  const visitorId = event.identification.visitorId;

  const profile = await db.visitorProfiles.findUnique({
    where: { visitorId },
    include: {
      cartItems: {
        include: { product: true },
        where: {
          savedAt: { gte: subDays(new Date(), 30) }, // Cart valid for 30 days
        },
      },
    },
  });

  if (profile?.cartItems.length > 0) {
    return res.json({
      isReturning: true,
      cart: {
        items: profile.cartItems,
        totalItems: profile.cartItems.reduce((sum, i) => sum + i.quantity, 0),
        message: "Welcome back! Your cart is waiting for you.",
      },
    });
  }

  return res.json({ isReturning: !!profile, cart: null });
});
```

**Frontend implementation:**

```typescript
const visitor = await identifyVisitor();

if (visitor.cart?.items.length > 0) {
  // Show notification
  showToast({
    title: "Welcome back!",
    message: `You have ${visitor.cart.totalItems} items in your cart`,
    action: {
      label: "View Cart",
      onClick: () => navigateTo("/cart"),
    },
  });

  // Restore cart state
  cartStore.setItems(visitor.cart.items);
}
```

#### SaaS: Streamlined Authentication

**Scenario:** Recognized device can log in with just email — no password or MFA needed.

```typescript
app.post("/api/login/start", async (req, res) => {
  const { email, guardianRequestId } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Find user
  const user = await db.users.findUnique({ where: { email } });
  if (!user) {
    return res.status(404).json({ error: "User not found" });
  }

  // Check if this device is recognized for this user
  const knownDevice = await db.userDevices.findFirst({
    where: {
      userId: user.id,
      visitorId,
      isVerified: true,
    },
  });

  if (knownDevice && !isBot(event) && !isTampering(event)) {
    // Recognized device — passwordless login
    const token = generateSessionToken(user);

    await db.userDevices.update({
      where: { id: knownDevice.id },
      data: { lastSeenAt: new Date() },
    });

    return res.json({
      success: true,
      token,
      loginMethod: "recognized_device",
      message: "Welcome back!",
    });
  }

  // Unknown device — require verification
  return res.json({
    requiresVerification: true,
    methods: knownDevice ? ["password"] : ["password", "magic_link"],
  });
});
```

#### Content Site: Personalized Feed

**Scenario:** News site shows articles based on visitor's reading history — no login required.

```typescript
app.get("/api/feed", async (req, res) => {
  const { guardianRequestId } = req.query;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Get visitor's reading history
  const history = await db.browsingHistory.findMany({
    where: { visitorId },
    orderBy: { viewedAt: "desc" },
    take: 100,
  });

  if (history.length === 0) {
    // New visitor — show trending content
    const trending = await getTrendingArticles();
    return res.json({ articles: trending, personalized: false });
  }

  // Analyze interests from reading history
  const categoryCounts = history.reduce((acc, item) => {
    acc[item.category] = (acc[item.category] || 0) + 1;
    return acc;
  }, {});

  const topCategories = Object.entries(categoryCounts)
    .sort(([, a], [, b]) => b - a)
    .slice(0, 3)
    .map(([category]) => category);

  // Get personalized articles
  const articles = await db.articles.findMany({
    where: {
      category: { in: topCategories },
      id: { notIn: history.map((h) => h.articleId) }, // Don't show already read
    },
    orderBy: { publishedAt: "desc" },
    take: 20,
  });

  return res.json({
    articles,
    personalized: true,
    interests: topCategories,
  });
});
```

#### Forms: Smart Pre-fill

**Scenario:** Checkout form remembers shipping address from previous purchases — even for guest checkout.

```typescript
app.get("/api/checkout/prefill", async (req, res) => {
  const { guardianRequestId } = req.query;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Get previously used addresses
  const previousOrders = await db.guestOrders.findMany({
    where: { visitorId },
    orderBy: { createdAt: "desc" },
    take: 1,
    select: {
      shippingAddress: true,
      billingAddress: true,
      email: true,
      phone: true,
    },
  });

  if (previousOrders.length === 0) {
    return res.json({ hasPrefill: false });
  }

  const lastOrder = previousOrders[0];

  return res.json({
    hasPrefill: true,
    prefill: {
      email: maskEmail(lastOrder.email), // Show "j***@example.com"
      phone: maskPhone(lastOrder.phone), // Show "***-***-1234"
      shippingAddress: lastOrder.shippingAddress,
      billingAddress: lastOrder.billingAddress,
    },
  });
});
```

**Frontend implementation:**

```typescript
const prefillData = await fetch(`/api/checkout/prefill?guardianRequestId=${requestId}`);

if (prefillData.hasPrefill) {
  showPrefillPrompt({
    message: "Use your saved information?",
    preview: `${prefillData.prefill.shippingAddress.city}, ${prefillData.prefill.shippingAddress.state}`,
    onConfirm: () => {
      fillForm(prefillData.prefill);
    },
    onDecline: () => {
      // User wants to enter new info
    },
  });
}
```

#### Multi-Session Onboarding

**Scenario:** User starts onboarding, leaves, returns later — picks up where they left off.

```typescript
app.post("/api/onboarding/progress", async (req, res) => {
  const { guardianRequestId, step, data } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Save progress
  await db.onboardingProgress.upsert({
    where: { visitorId },
    create: {
      visitorId,
      currentStep: step,
      stepData: data,
      startedAt: new Date(),
      lastUpdatedAt: new Date(),
    },
    update: {
      currentStep: step,
      stepData: data,
      lastUpdatedAt: new Date(),
    },
  });

  return res.json({ success: true });
});

app.get("/api/onboarding/resume", async (req, res) => {
  const { guardianRequestId } = req.query;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  const progress = await db.onboardingProgress.findUnique({
    where: { visitorId },
  });

  if (!progress || progress.currentStep === "completed") {
    return res.json({ hasProgress: false });
  }

  return res.json({
    hasProgress: true,
    currentStep: progress.currentStep,
    savedData: progress.stepData,
    startedAt: progress.startedAt,
  });
});
```

***

### Linking Visitors to Accounts

When an anonymous visitor signs up or logs in, merge their visitor profile with their account:

```typescript
app.post("/api/auth/complete", async (req, res) => {
  const { userId, guardianRequestId } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Link visitor profile to user account
  const visitorProfile = await db.visitorProfiles.findUnique({
    where: { visitorId },
    include: {
      cartItems: true,
      preferences: true,
      browsingHistory: true,
    },
  });

  if (visitorProfile) {
    // Merge cart items
    if (visitorProfile.cartItems.length > 0) {
      await db.userCartItems.createMany({
        data: visitorProfile.cartItems.map((item) => ({
          userId,
          productId: item.productId,
          quantity: item.quantity,
        })),
        skipDuplicates: true,
      });
    }

    // Merge preferences
    if (visitorProfile.preferences) {
      await db.userPreferences.upsert({
        where: { userId },
        create: {
          userId,
          ...visitorProfile.preferences,
        },
        update: visitorProfile.preferences,
      });
    }

    // Link visitor to user for future recognition
    await db.userDevices.create({
      data: {
        userId,
        visitorId,
        isVerified: true,
        firstSeenAt: visitorProfile.firstSeenAt,
        lastSeenAt: new Date(),
      },
    });
  }

  return res.json({ success: true, profileMerged: !!visitorProfile });
});
```

***

### Database Schema Example

```sql
-- Anonymous visitor profiles
CREATE TABLE visitor_profiles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255) UNIQUE NOT NULL,
  first_seen_at TIMESTAMP DEFAULT NOW(),
  last_seen_at TIMESTAMP DEFAULT NOW(),
  visit_count INTEGER DEFAULT 1
);

CREATE INDEX idx_visitor_profiles_visitor_id 
  ON visitor_profiles(visitor_id);

-- Visitor preferences (anonymous)
CREATE TABLE visitor_preferences (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255) UNIQUE REFERENCES visitor_profiles(visitor_id),
  language VARCHAR(10),
  currency VARCHAR(3),
  theme VARCHAR(20),
  notifications_enabled BOOLEAN DEFAULT true,
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Anonymous cart items
CREATE TABLE visitor_cart_items (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255) REFERENCES visitor_profiles(visitor_id),
  product_id UUID NOT NULL,
  quantity INTEGER DEFAULT 1,
  saved_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_visitor_cart_visitor 
  ON visitor_cart_items(visitor_id);

-- Browsing history for recommendations
CREATE TABLE visitor_browsing_history (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255) REFERENCES visitor_profiles(visitor_id),
  product_id UUID,
  article_id UUID,
  category VARCHAR(100),
  view_duration INTEGER, -- seconds
  viewed_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_browsing_history_visitor 
  ON visitor_browsing_history(visitor_id, viewed_at DESC);

-- Onboarding progress
CREATE TABLE onboarding_progress (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  visitor_id VARCHAR(255) UNIQUE,
  current_step VARCHAR(50),
  step_data JSONB,
  started_at TIMESTAMP DEFAULT NOW(),
  last_updated_at TIMESTAMP DEFAULT NOW()
);

-- Link visitors to user accounts
CREATE TABLE user_devices (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  visitor_id VARCHAR(255) NOT NULL,
  is_verified BOOLEAN DEFAULT false,
  device_name VARCHAR(255),
  first_seen_at TIMESTAMP,
  last_seen_at TIMESTAMP DEFAULT NOW(),
  
  UNIQUE(user_id, visitor_id)
);
```

***

### Privacy Considerations

Guardian's `visitorId` is privacy-friendly:

* **No personal data required** — Recognition works without email, phone, or name
* **Device-based, not person-based** — Identifies the device, not the individual
* **User control** — Clearing browser data resets identity (if they want anonymity)
* **GDPR compliant** — No cookies, no cross-site tracking
* **Transparent** — Users can see and manage their saved preferences

**Best practice:** Let users opt out of personalization:

```typescript
app.post("/api/preferences/reset", async (req, res) => {
  const { guardianRequestId } = req.body;

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Delete all visitor data
  await db.visitorProfiles.delete({ where: { visitorId } });

  return res.json({
    success: true,
    message: "Your browsing data has been cleared",
  });
});
```

***

### Best Practices

#### Do

* **Recognize early** — Call Guardian on page load, not just at checkout
* **Be helpful, not creepy** — "Your cart is waiting" is good; "We know you looked at X 47 times" is not
* **Offer control** — Let users clear their data or opt out of personalization
* **Merge on signup** — Transfer anonymous preferences to user accounts
* **Handle gracefully** — If recognition fails, fall back to default experience
* **Respect privacy** — Don't expose `visitorId` to users or third parties

#### Don't

* **Don't require login for personalization** — That defeats the purpose
* **Don't be too aggressive** — Subtle personalization beats obvious tracking
* **Don't store sensitive data** — Financial info, health data, etc. should require authentication
* **Don't assume one device = one person** — Shared devices exist

***

### Conclusion

Every returning visitor is an opportunity. Without recognition, you treat loyal customers like strangers — making them log in repeatedly, re-enter information, and start from scratch.

Guardian Stack's persistent `visitorId` lets you:

* **Recognize visitors instantly** — Before they log in, without cookies
* **Personalize from the first click** — Show relevant content, restore preferences
* **Reduce friction** — Pre-fill forms, streamline authentication
* **Recover abandoned carts** — Bring visitors back to where they left off
* **Build loyalty** — Create seamless experiences that feel personal

The key insight: You don't need personal information to create personal experiences. Device recognition gives you continuity without compromising privacy.

***

{% hint style="success" %}
**Get Started:** [Sign up for Guardian Stack](https://dashboard.guardianstack.ai/) and start recognizing your visitors today.
{% endhint %}


# Web3 Fraud Prevention

Your smart contract is blind to who calls it. Add an identity layer with cryptographic signatures that block bots, scripts, and multi-wallet farming.

### The Problem

Smart contracts are blind to who calls them. When a user interacts directly with your smart contract (via Etherscan, a script, or any Web3 wallet), they bypass your frontend entirely, and any fraud detection you've implemented there.

This creates vulnerabilities in:

* **NFT marketplaces**: Wash trading, bot sniping during drops, Sybil attacks for airdrops
* **DeFi platforms**: Multi-account farming, bot-driven arbitrage abuse, reward exploitation
* **Token sales**: Bot purchases, unfair distribution, automated sniping
* **Web3 gaming**: Multi-accounting, automated gameplay, reward farming
* **DAOs**: Sybil attacks on voting, proposal spam, governance manipulation

### The Solution: Signature-Based Verification

GuardianStack acts as an **identity oracle** for your smart contracts. Here's how it works:

1. User interacts with your **frontend**
2. GuardianStack verifies they're a real human (not a bot/script)
3. Your **backend** checks the Guardian event and generates a **cryptographic signature**
4. User's wallet submits the transaction **with the signature**
5. Your **smart contract** validates the signature before executing

<div data-with-frame="true"><figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FxVUVROMeLqrsPB8aaiqk%2FGuardian-Powered%20Registration%20Flow%20-%20visual%20selection%20(2).png?alt=media&amp;token=44e54c71-723b-4fb1-9cbe-31123c3bac60" alt=""><figcaption></figcaption></figure></div>

{% hint style="success" %}
**Result**: Even if someone calls your contract directly, the transaction fails without a valid Guardian signature.
{% endhint %}

***

### Implementation Guide

#### Step 1: Frontend - Collect Guardian Event

Install the Guardian SDK:

```bash
npm install @guardianstack/guardian-js
```

Initialize Guardian and get the `requestId`:

```typescript
import { loadAgent } from "@guardianstack/guardian-js";

async function getGuardianRequestId(): Promise<string> {
  const agent = await loadAgent({
    siteKey: "YOUR_SITE_KEY",
  });

  const response = await agent.get();
  
  return response?.requestId;
}
```

#### Step 2: Backend - Verify & Generate Signature

Install the Guardian Server SDK:

```bash
npm install @guardianstack/guardianjs-server
```

Create an endpoint that verifies the Guardian event and returns a signature:

```typescript
import {
  createGuardianClient,
  isBot,
  isVPN,
  isTampering,
  isVirtualized,
} from "@guardianstack/guardianjs-server";
import { ethers } from "ethers";

const guardianClient = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET!,
});

// Your backend signer (keep this private!)
const signer = new ethers.Wallet(process.env.BACKEND_PRIVATE_KEY!);

interface SignatureRequest {
  requestId: string;      // Guardian event ID
  userAddress: string;    // User's wallet address
  action: string;         // e.g., "mint", "trade", "claim"
  params?: any;           // Action-specific parameters
}

app.post("/api/get-signature", async (req, res) => {
  const { requestId, userAddress, action, params } = req.body as SignatureRequest;

  // 1. Fetch Guardian event
  const event = await guardianClient.getEvent(requestId);

  // 2. Apply your fraud rules
  if (isBot(event)) {
    return res.status(403).json({ error: "Bot detected" });
  }

  if (isVPN(event) || isTampering(event) || isVirtualized(event)) {
    return res.status(403).json({ error: "Suspicious activity detected" });
  }

  // Optional: Additional checks
  const botScore = event.bot?.score ?? 0;
  if (botScore > 80) {
    return res.status(403).json({ error: "High bot score" });
  }

  // 3. Generate signature
  const expiresAt = Math.floor(Date.now() / 1000) + 300; // 5 minutes

  // Create message hash (EIP-712 recommended for production)
  const messageHash = ethers.utils.solidityKeccak256(
    ["address", "string", "uint256", "bytes32"],
    [
      userAddress,
      action,
      expiresAt,
      ethers.utils.keccak256(ethers.utils.toUtf8Bytes(JSON.stringify(params || {}))),
    ]
  );

  // Sign the message
  const signature = await signer.signMessage(ethers.utils.arrayify(messageHash));

  res.json({
    signature,
    expiresAt,
    messageHash,
  });
});
```

#### Step 3: Frontend - Request Signature & Submit Transaction

```typescript
import { ethers } from "ethers";

async function mintNFT() {
  // 1. Get Guardian requestId
  const requestId = await getGuardianRequestId();

  // 2. Get user's wallet
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();
  const userAddress = await signer.getAddress();

  // 3. Request signature from your backend
  const response = await fetch("/api/get-signature", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      requestId,
      userAddress,
      action: "mint",
      params: { tokenId: 123 },
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || "Failed to get signature");
  }

  const { signature, expiresAt, messageHash } = await response.json();

  // 4. Call smart contract with signature
  const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
  
  const tx = await contract.mint(
    userAddress,
    123, // tokenId
    expiresAt,
    signature
  );

  await tx.wait();
  console.log("Minted successfully!");
}
```

#### Step 4: Smart Contract - Verify Signature

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract GuardianProtectedNFT is Ownable {
    using ECDSA for bytes32;

    address public guardianSigner;

    constructor(address _guardianSigner) {
        guardianSigner = _guardianSigner;
    }

    function setGuardianSigner(address _newSigner) external onlyOwner {
        guardianSigner = _newSigner;
    }

    modifier onlyVerified(
        address user,
        string memory action,
        uint256 expiresAt,
        bytes32 paramsHash,
        bytes memory signature
    ) {
        // 1. Check expiration
        require(block.timestamp <= expiresAt, "Signature expired");

        // 2. Reconstruct message hash
        bytes32 messageHash = keccak256(
            abi.encodePacked(user, action, expiresAt, paramsHash)
        );

        // 3. Verify signature
        bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash();
        address recoveredSigner = ethSignedMessageHash.recover(signature);

        require(recoveredSigner == guardianSigner, "Invalid signature");
        _;
    }

    function mint(
        address to,
        uint256 tokenId,
        uint256 expiresAt,
        bytes memory signature
    ) external onlyVerified(
        to,
        "mint",
        expiresAt,
        keccak256(abi.encodePacked('{"tokenId":', tokenId, '}')),
        signature
    ) {
        // Your mint logic here
        _safeMint(to, tokenId);
    }
}
```

***

### Use Case Examples

#### NFT Marketplace - Prevent Wash Trading

**Problem**: Users create multiple wallets to trade with themselves and inflate prices.

**Solution**: Link all transactions to Guardian's `visitorId` (persistent device fingerprint).

```typescript
// Backend: Store visitorId with each signature
app.post("/api/get-trade-signature", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.requestId);
  
  const visitorId = event.visitorId;
  
  // Check if buyer and seller share the same visitorId
  const buyerVisitorId = await getVisitorIdForAddress(req.body.buyerAddress);
  const sellerVisitorId = await getVisitorIdForAddress(req.body.sellerAddress);
  
  if (buyerVisitorId === sellerVisitorId) {
    return res.status(403).json({ error: "Wash trading detected" });
  }
  
  // Store visitorId for future checks
  await storeVisitorId(req.body.buyerAddress, visitorId);
  
  // Generate signature...
});
```

#### DeFi - Prevent Reward Farming

**Problem**: Users create hundreds of accounts to farm airdrops/rewards.

**Solution**: Rate-limit rewards per device and detect virtualized environments.

```typescript
app.post("/api/get-claim-signature", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.requestId);
  
  // Detect virtual machines (common in farming operations)
  if (isVirtualized(event)) {
    return res.status(403).json({ error: "Virtualized environment detected" });
  }
  
  // Check how many claims from this device in the last 24h
  const visitorId = event.visitorId;
  const recentClaims = await getClaimCountByVisitor(visitorId, 24 * 60 * 60 * 1000);
  
  if (recentClaims >= 5) {
    return res.status(403).json({ error: "Too many claims from this device" });
  }
  
  // Generate signature...
});
```

#### Token Sale - Fair Launch

**Problem**: Bots buy entire supply in milliseconds.

**Solution**: Require human verification and rate-limit per device.

```typescript
app.post("/api/get-purchase-signature", async (req, res) => {
  const event = await guardianClient.getEvent(req.body.requestId);
  
  // Block bots
  if (isBot(event)) {
    return res.status(403).json({ error: "Bot detected" });
  }
  
  // Block high-risk indicators
  const botScore = event.bot?.score ?? 0;
  const tamperingScore = event.tampering?.score ?? 0;
  
  if (botScore > 70 || tamperingScore > 70) {
    return res.status(403).json({ error: "Suspicious activity" });
  }
  
  // Limit purchases per device
  const visitorId = event.visitorId;
  const purchaseCount = await getPurchaseCountByVisitor(visitorId);
  
  if (purchaseCount >= 1) {
    return res.status(403).json({ error: "Purchase limit reached" });
  }
  
  // Generate signature...
});
```

***

### Advanced: EIP-712 Typed Data Signing

For production applications, use EIP-712 for better UX and security:

```typescript
// Backend
const domain = {
  name: "MyProtocol",
  version: "1",
  chainId: 1,
  verifyingContract: CONTRACT_ADDRESS,
};

const types = {
  Action: [
    { name: "user", type: "address" },
    { name: "action", type: "string" },
    { name: "expiresAt", type: "uint256" },
    { name: "nonce", type: "uint256" },
  ],
};

const value = {
  user: userAddress,
  action: "mint",
  expiresAt,
  nonce: await getNonce(userAddress),
};

const signature = await signer._signTypedData(domain, types, value);
```

```solidity
// Smart Contract
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

contract GuardianProtectedNFT is EIP712, Ownable {
    bytes32 private constant ACTION_TYPEHASH =
        keccak256("Action(address user,string action,uint256 expiresAt,uint256 nonce)");

    mapping(address => uint256) public nonces;

    constructor() EIP712("MyProtocol", "1") {}

    function mint(
        address to,
        string memory action,
        uint256 expiresAt,
        bytes memory signature
    ) external {
        require(block.timestamp <= expiresAt, "Signature expired");

        bytes32 structHash = keccak256(
            abi.encode(ACTION_TYPEHASH, to, keccak256(bytes(action)), expiresAt, nonces[to])
        );

        bytes32 digest = _hashTypedDataV4(structHash);
        address signer = ECDSA.recover(digest, signature);

        require(signer == guardianSigner, "Invalid signature");

        nonces[to]++;
        _safeMint(to, tokenId);
    }
}
```

### Conclusion

Traditional Web3 security focuses on smart contract vulnerabilities: reentrancy attacks, integer overflows, access control bugs. But the biggest threat to your protocol isn't a code exploit; it's **systematic abuse by bots and fraudsters**.

GuardianStack solves this by bringing **identity verification** to the blockchain without compromising decentralization. Your smart contracts remain permissionless and trustless, but they gain the ability to distinguish between legitimate users and automated attackers.

#### Key Benefits

✅ **Stop bots at the contract level** - Not just your frontend\
✅ **Prevent Sybil attacks** - Link wallets to real devices\
✅ **Fair token distributions** - No more bot-dominated launches\
✅ **Protect protocol economics** - Stop multi-account farming\
✅ **Maintain decentralization** - Users still control their keys

#### The Bottom Line

If your protocol has value, it will be attacked. Bots will farm it. Scripts will exploit it. Bad actors will game it.

GuardianStack gives you the tools to fight back without sacrificing the core principles of Web3.


# Employee Device Trust

#### The Problem

When an employee gets phished, the attacker steals their credentials, and often walks right in. Traditional security layers fail at the one moment that matters most: the login from an unfamiliar device.

* **Schools:** Staff with access to student records, grades, and financial aid data
* **Construction:** Project managers with access to bids, contracts, and payroll systems
* **Healthcare:** Administrative staff accessing patient scheduling and billing portals
* **Logistics:** Dispatchers and warehouse staff using fleet management and inventory systems
* **Professional Services:** Accountants, lawyers, and consultants accessing client-sensitive portals

These organizations share a common vulnerability: **non-technical employees accessing sensitive web applications** who are prime phishing targets.

Here's why existing defenses fail when credentials are compromised:

| Defense             | Why It Fails After Phishing                                        |
| ------------------- | ------------------------------------------------------------------ |
| **Passwords**       | Stolen directly — the attacker has the exact credentials           |
| **SMS/Email MFA**   | Intercepted by real-time phishing proxies (EvilGinx, Modlishka)    |
| **TOTP Codes**      | Captured in real-time by adversary-in-the-middle kits              |
| **IP Allowlisting** | Attackers use residential proxies to match the victim's geo-region |
| **Session Cookies** | Stolen via phishing kits that harvest tokens post-authentication   |

{% hint style="danger" %}
**The result:** An attacker with stolen credentials can log in from *any* device, and your application has no way to tell them apart from the real employee.
{% endhint %}

***

#### The Solution: Device Trust as a Security Layer

Guardian Stack's `visitorId` creates a persistent, tamper-resistant device identity for every browser that touches your internal applications. When an attacker phishes an employee's password and MFA token, there's one thing they **cannot steal: the device fingerprint**.

**How It Stops Phishing-Based Account Takeover**

<figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FtD4RFWPiECmQu6DGaTiv%2FGuardian-Powered%20Registration%20Flow%20-%20visual%20selection.png?alt=media&amp;token=a35bef49-23c4-4ebd-832a-48c865942c18" alt="" width="506"><figcaption></figcaption></figure>

1. Employee logs in from their work machine - Guardian silently records the device's `visitorId`
2. Device becomes **trusted** after initial verification, linked to the employee's account
3. Attacker phishes credentials and attempts to log in from their own machine
4. Guardian sees: **unknown `visitorId`**, VPN detected, possible VM or browser tampering
5. System **blocks or challenges** - even though the password and MFA token are valid

**What Guardian Detects on the Attacker's Device**

The attacker's environment almost always triggers multiple risk signals:

* **Unknown `visitorId`** - The device has never been seen before for this account
* **VPN detected** - Attackers route traffic through VPNs to mask their true location
* **Browser tampering** - Anti-detect browsers and spoofed user agents trigger tampering signals
* **Virtualization** - Many attackers operate from virtual machines to isolate their activity
* **Incognito mode** - Commonly used to avoid leaving traces
* **Privacy settings** - Aggressive fingerprint resistance configurations

{% hint style="info" %}
**Key insight:** Phishing steals *credentials*, not *devices*. Guardian Stack turns the device itself into an authentication factor that cannot be phished, forwarded, or replayed.
{% endhint %}

***

#### Implementation Guide

**Step 1: Frontend** - **Identify the Device on Every Login**

Install the Guardian JS SDK on your internal web application's login page:

```bash
npm install @guardianstack/guardian-js
```

Trigger identification when the employee submits their login form:

```typescript
import { loadAgent } from "@guardianstack/guardian-js";

// Initialize Guardian once when the login page loads
const guardian = await loadAgent({
  siteKey: "YOUR_SITE_KEY",
});

// Capture device identity at login submission
async function handleLogin(email: string, password: string) {
  // 1. Get the device fingerprint
  const response = await guardian.get();
  const requestId = response?.requestId;

  // 2. Send credentials + device identity to your backend
  const result = await fetch("/api/auth/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      email,
      password,
      guardianRequestId: requestId,
    }),
  });

  return result.json();
}
```

**Step 2: Backend - Verify Device Trust Before Granting Access**

Install the Guardian Server SDK:

```bash
npm install @guardianstack/guardianjs-server
```

Create a login endpoint that evaluates device trust alongside credentials:

```typescript
import {
  createGuardianClient,
  isBot,
} from "@guardianstack/guardianjs-server";

const guardianClient = createGuardianClient({
  secret: process.env.GUARDIAN_SECRET_KEY!,
});

app.post("/api/auth/login", async (req, res) => {
  const { email, password, guardianRequestId } = req.body;

  // 1. Validate credentials (your existing auth logic)
  const user = await authenticateUser(email, password);
  if (!user) {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  // 2. Fetch Guardian event for device intelligence
  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // 3. Check for high-risk device signals
  const deviceRisk = assessDeviceRisk(event);

  // 4. Check if this device is trusted for this user
  const trustedDevice = await db.trustedDevices.findFirst({
    where: {
      userId: user.id,
      visitorId,
      isActive: true,
    },
  });

  // 5. Make access decision
  if (deviceRisk.isHighRisk) {
    // Block entirely — likely an attacker
    await logSecurityEvent({
      userId: user.id,
      visitorId,
      action: "LOGIN_BLOCKED",
      reason: deviceRisk.reasons,
      ip: event.ip,
    });

    return res.status(403).json({
      error: "Access denied",
      message: "This login attempt has been blocked for security reasons. "
             + "Contact your IT administrator if this was you.",
    });
  }

  if (!trustedDevice) {
    // Unknown device — require additional verification
    const challenge = await createDeviceChallenge(user, visitorId);

    return res.json({
      requiresVerification: true,
      challengeId: challenge.id,
      methods: ["admin_approval", "email_verification"],
      message: "New device detected. Additional verification required.",
    });
  }

  // 6. Trusted device, no risk signals — grant access
  await db.trustedDevices.update({
    where: { id: trustedDevice.id },
    data: { lastSeenAt: new Date() },
  });

  const token = generateSessionToken(user);
  return res.json({ success: true, token });
});
```

**Step 3: Device Risk Assessment**

Build a risk scoring function using Guardian's detection signals:

```typescript
interface DeviceRisk {
  isHighRisk: boolean;
  isMediumRisk: boolean;
  score: number;       // 0–100
  reasons: string[];
}

function assessDeviceRisk(event: GuardianEvent): DeviceRisk {
  const reasons: string[] = [];
  let score = 0;

  // Bot detection — automated attack, block immediately
  if (isBot(event)) {
    reasons.push("Automated bot detected");
    score += 50;
  }

  // Browser tampering — anti-detect browsers, spoofed fingerprints
  if (event.tampering?.detected) {
    reasons.push("Browser tampering detected");
    score += 40;
  }

  // Virtual machine — attackers isolate activity in VMs
  if (event.virtualization?.detected) {
    reasons.push("Virtual machine detected");
    score += 30;
  }

  // VPN — masking true location
  if (event.vpn?.detected) {
    reasons.push("VPN detected");
    score += 20;
  }

  // Incognito mode — avoiding fingerprint persistence
  if (event.incognito?.detected) {
    reasons.push("Incognito mode detected");
    score += 15;
  }

  // Privacy settings — aggressive anti-fingerprinting
  if (event.privacySettings?.detected) {
    reasons.push("Enhanced privacy settings detected");
    score += 10;
  }

  return {
    isHighRisk: score >= 50,
    isMediumRisk: score >= 25 && score < 50,
    score: Math.min(score, 100),
    reasons,
  };
}
```

**Step 4: Device Trust Enrollment**

When a new device passes verification, enroll it as trusted:

```typescript
app.post("/api/auth/verify-device", async (req, res) => {
  const { challengeId, verificationCode, guardianRequestId } = req.body;

  // 1. Validate the challenge
  const challenge = await db.deviceChallenges.findUnique({
    where: { id: challengeId },
  });

  if (!challenge || challenge.expiresAt < new Date()) {
    return res.status(400).json({ error: "Challenge expired" });
  }

  // 2. Verify (email code, admin approval, etc.)
  const isValid = await verifyChallenge(challenge, verificationCode);
  if (!isValid) {
    return res.status(401).json({ error: "Invalid verification" });
  }

  // 3. Fetch device identity
  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // 4. Enroll the device as trusted
  await db.trustedDevices.create({
    data: {
      userId: challenge.userId,
      visitorId,
      deviceName: deriveDeviceName(event), // e.g. "Chrome on Windows"
      enrolledAt: new Date(),
      enrolledBy: "email_verification",
      lastSeenAt: new Date(),
      isActive: true,
    },
  });

  // 5. Grant access
  const user = await db.users.findUnique({ where: { id: challenge.userId } });
  const token = generateSessionToken(user);

  return res.json({
    success: true,
    token,
    message: "Device registered. You won't be asked again on this device.",
  });
});
```

***

#### Real-World Examples

**School: Protecting Student Records**

**Scenario:** A school administrator's credentials are phished via a fake password reset email. The attacker tries to access the student information system to steal records.

```typescript
app.post("/api/auth/login", async (req, res) => {
  const { email, password, guardianRequestId } = req.body;

  const user = await authenticateUser(email, password);
  if (!user) {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;
  const deviceRisk = assessDeviceRisk(event);

  // For roles with access to student PII, enforce strict device trust
  const requiresStrictTrust = ["admin", "registrar", "counselor", "principal"]
    .includes(user.role);

  if (requiresStrictTrust) {
    const trustedDevice = await db.trustedDevices.findFirst({
      where: { userId: user.id, visitorId, isActive: true },
    });

    if (!trustedDevice) {
      // Unknown device accessing student data — always challenge
      await notifyITAdmin({
        message: `New device login attempt for ${user.role}: ${user.email}`,
        riskScore: deviceRisk.score,
        reasons: deviceRisk.reasons,
      });

      return res.json({
        requiresVerification: true,
        message: "New device detected. IT admin has been notified for approval.",
      });
    }

    if (deviceRisk.isMediumRisk || deviceRisk.isHighRisk) {
      // Known device but exhibiting risk signals — possible compromise
      await lockAccount(user.id, "Suspicious device signals on trusted device");

      return res.status(403).json({
        error: "Account locked",
        message: "Unusual activity detected. Contact IT support.",
      });
    }
  }

  // Low-risk, trusted device — grant access
  const token = generateSessionToken(user);
  return res.json({ success: true, token });
});
```

**What happens when the attacker tries to log in:**

```
✅ Password:    Correct (phished)
✅ MFA Token:   Correct (intercepted by real-time proxy)
❌ Device:      Unknown visitorId
❌ Environment: VPN detected, incognito mode
❌ Risk Score:  35+ → blocked

→ Login denied. IT admin notified. Account locked.
```

**Construction Company: Protecting Bid and Payroll Systems**

**Scenario:** A project manager's credentials are stolen via a spear-phishing email disguised as a subcontractor invoice. The attacker wants access to bid pricing and payroll data.

```typescript
// Middleware that runs on every request to sensitive internal tools
async function deviceTrustMiddleware(req, res, next) {
  const { guardianRequestId } = req.headers;
  const userId = req.session.userId;

  if (!guardianRequestId) {
    return res.status(400).json({ error: "Device verification required" });
  }

  const event = await guardianClient.getEvent(guardianRequestId);
  const visitorId = event.identification.visitorId;

  // Check device trust
  const trustedDevice = await db.trustedDevices.findFirst({
    where: { userId, visitorId, isActive: true },
  });

  if (!trustedDevice) {
    return res.status(403).json({
      error: "Unrecognized device",
      message: "This device is not authorized. Contact your supervisor.",
    });
  }

  // Continuous monitoring — check for mid-session anomalies
  const deviceRisk = assessDeviceRisk(event);
  if (deviceRisk.isHighRisk) {
    // Session might be hijacked — terminate
    await terminateSession(req.session.id);
    await logSecurityEvent({
      userId,
      visitorId,
      action: "SESSION_TERMINATED",
      reason: "High-risk signals detected mid-session",
      details: deviceRisk.reasons,
    });

    return res.status(403).json({
      error: "Session terminated",
      message: "Security anomaly detected. Please log in again.",
    });
  }

  // Update last seen
  await db.trustedDevices.update({
    where: { id: trustedDevice.id },
    data: { lastSeenAt: new Date() },
  });

  req.deviceTrust = { visitorId, risk: deviceRisk, device: trustedDevice };
  next();
}

// Apply to all sensitive routes
app.use("/api/bids", deviceTrustMiddleware);
app.use("/api/payroll", deviceTrustMiddleware);
app.use("/api/contracts", deviceTrustMiddleware);
```

**Role-Based Device Policies**

**Scenario:** Different employee roles require different levels of device trust enforcement.

```typescript
interface DevicePolicy {
  requireTrustedDevice: boolean;
  allowMediumRisk: boolean;
  maxDevicesPerUser: number;
  deviceTrustExpiryDays: number;
  requireAdminApproval: boolean;
}

const DEVICE_POLICIES: Record<string, DevicePolicy> = {
  // Executives and finance — strictest controls
  executive: {
    requireTrustedDevice: true,
    allowMediumRisk: false,
    maxDevicesPerUser: 2,
    deviceTrustExpiryDays: 30,
    requireAdminApproval: true,
  },

  // Staff with access to sensitive records
  staff_sensitive: {
    requireTrustedDevice: true,
    allowMediumRisk: false,
    maxDevicesPerUser: 3,
    deviceTrustExpiryDays: 60,
    requireAdminApproval: true,
  },

  // General staff
  staff_general: {
    requireTrustedDevice: true,
    allowMediumRisk: true,
    maxDevicesPerUser: 5,
    deviceTrustExpiryDays: 90,
    requireAdminApproval: false,
  },

  // Contractors and temporary workers
  contractor: {
    requireTrustedDevice: true,
    allowMediumRisk: false,
    maxDevicesPerUser: 1,
    deviceTrustExpiryDays: 14,
    requireAdminApproval: true,
  },
};

async function enforceDevicePolicy(
  user: User,
  visitorId: string,
  deviceRisk: DeviceRisk
): Promise<{ allowed: boolean; reason?: string }> {
  const policy = DEVICE_POLICIES[user.role] || DEVICE_POLICIES.staff_general;

  // Check device trust
  if (policy.requireTrustedDevice) {
    const trustedDevice = await db.trustedDevices.findFirst({
      where: {
        userId: user.id,
        visitorId,
        isActive: true,
        lastSeenAt: {
          gte: subDays(new Date(), policy.deviceTrustExpiryDays),
        },
      },
    });

    if (!trustedDevice) {
      return {
        allowed: false,
        reason: "Device not trusted or trust has expired",
      };
    }
  }

  // Check risk tolerance
  if (!policy.allowMediumRisk && deviceRisk.isMediumRisk) {
    return {
      allowed: false,
      reason: "Medium-risk signals not allowed for this role",
    };
  }

  if (deviceRisk.isHighRisk) {
    return { allowed: false, reason: "High-risk device signals detected" };
  }

  return { allowed: true };
}
```

**IT Admin Dashboard: Device Fleet Visibility**

**Scenario:** IT admin needs to see all trusted devices across the organization, revoke compromised devices, and monitor login anomalies.

```typescript
// Get all trusted devices for the organization
app.get("/api/admin/devices", async (req, res) => {
  const devices = await db.trustedDevices.findMany({
    include: { user: { select: { email: true, role: true, name: true } } },
    orderBy: { lastSeenAt: "desc" },
  });

  return res.json({ devices });
});

// Revoke a specific device (e.g. lost laptop, terminated employee)
app.post("/api/admin/devices/revoke", async (req, res) => {
  const { deviceId, reason } = req.body;

  const device = await db.trustedDevices.update({
    where: { id: deviceId },
    data: {
      isActive: false,
      revokedAt: new Date(),
      revokedReason: reason,
    },
  });

  // Terminate any active sessions from this device
  await db.sessions.deleteMany({
    where: { userId: device.userId, visitorId: device.visitorId },
  });

  await logSecurityEvent({
    userId: device.userId,
    visitorId: device.visitorId,
    action: "DEVICE_REVOKED",
    reason,
    performedBy: req.session.userId,
  });

  return res.json({ success: true });
});

// Revoke all devices for a user (employee termination or compromise)
app.post("/api/admin/devices/revoke-all", async (req, res) => {
  const { userId, reason } = req.body;

  await db.trustedDevices.updateMany({
    where: { userId, isActive: true },
    data: {
      isActive: false,
      revokedAt: new Date(),
      revokedReason: reason,
    },
  });

  // Kill all sessions
  await db.sessions.deleteMany({ where: { userId } });

  return res.json({ success: true });
});

// Get security events log
app.get("/api/admin/security-log", async (req, res) => {
  const events = await db.securityEvents.findMany({
    where: {
      createdAt: { gte: subDays(new Date(), 30) },
    },
    include: { user: { select: { email: true, name: true } } },
    orderBy: { createdAt: "desc" },
    take: 500,
  });

  return res.json({ events });
});
```

***

#### Database Schema

```sql
-- Trusted devices linked to employee accounts
CREATE TABLE trusted_devices (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  visitor_id VARCHAR(255) NOT NULL,
  device_name VARCHAR(255),          -- e.g. "Chrome on Windows"
  enrolled_at TIMESTAMP DEFAULT NOW(),
  enrolled_by VARCHAR(50),           -- "email_verification", "admin_approval"
  last_seen_at TIMESTAMP DEFAULT NOW(),
  is_active BOOLEAN DEFAULT true,
  revoked_at TIMESTAMP,
  revoked_reason TEXT,

  UNIQUE(user_id, visitor_id)
);

CREATE INDEX idx_trusted_devices_user
  ON trusted_devices(user_id, is_active);
CREATE INDEX idx_trusted_devices_visitor
  ON trusted_devices(visitor_id);

-- Device verification challenges
CREATE TABLE device_challenges (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id),
  visitor_id VARCHAR(255) NOT NULL,
  challenge_type VARCHAR(50) NOT NULL, -- "email", "admin_approval"
  verification_code VARCHAR(255),
  status VARCHAR(20) DEFAULT 'pending', -- "pending", "approved", "denied", "expired"
  created_at TIMESTAMP DEFAULT NOW(),
  expires_at TIMESTAMP NOT NULL,
  resolved_at TIMESTAMP,
  resolved_by UUID                      -- admin who approved/denied
);

CREATE INDEX idx_device_challenges_status
  ON device_challenges(user_id, status);

-- Security event log (audit trail)
CREATE TABLE security_events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id),
  visitor_id VARCHAR(255),
  action VARCHAR(50) NOT NULL,       -- "LOGIN_BLOCKED", "DEVICE_REVOKED", etc.
  reason TEXT[],
  risk_score INTEGER,
  ip_address INET,
  metadata JSONB,
  performed_by UUID,                 -- for admin actions
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_security_events_user
  ON security_events(user_id, created_at DESC);
CREATE INDEX idx_security_events_action
  ON security_events(action, created_at DESC);
```

***

#### Deployment Patterns

**Pattern 1: Login-Only Protection (Quick Start)**

Add device trust checks only at the login endpoint. This is the fastest way to protect against phished credentials with minimal code changes.

**Best for:** Organizations that want immediate protection without modifying their entire application.

```typescript
// Add to your existing login handler — no other changes needed
const event = await guardianClient.getEvent(guardianRequestId);
const visitorId = event.identification.visitorId;
const deviceRisk = assessDeviceRisk(event);

const trustedDevice = await db.trustedDevices.findFirst({
  where: { userId: user.id, visitorId, isActive: true },
});

if (deviceRisk.isHighRisk || !trustedDevice) {
  // Challenge or block
}
```

**Pattern 2: Continuous Session Monitoring**

Re-verify device identity on every sensitive action, not just at login. This catches session hijacking and stolen session tokens.

**Best for:** Organizations with high-value data (student records, financial systems, healthcare).

```typescript
// Middleware on every API request
app.use("/api/sensitive/*", async (req, res, next) => {
  const event = await guardianClient.getEvent(req.headers["x-guardian-request-id"]);
  const deviceRisk = assessDeviceRisk(event);

  if (deviceRisk.isHighRisk) {
    await terminateSession(req.session.id);
    return res.status(403).json({ error: "Session terminated" });
  }

  next();
});
```

**Pattern 3: Tiered Access Based on Device Trust**

Allow login from any device, but restrict what data the user can access based on device trust level.

**Best for:** Organizations where employees sometimes need to access systems from personal or shared devices.

```typescript
app.get("/api/data/:resource", async (req, res) => {
  const event = await guardianClient.getEvent(req.headers["x-guardian-request-id"]);
  const visitorId = event.identification.visitorId;

  const trustedDevice = await db.trustedDevices.findFirst({
    where: { userId: req.user.id, visitorId, isActive: true },
  });

  const accessLevel = trustedDevice ? "full" : "limited";

  if (accessLevel === "limited") {
    // Allow viewing, but not exporting or modifying
    return res.json({
      data: await getReadOnlyView(req.params.resource),
      accessLevel: "limited",
      message: "Full access requires a trusted device.",
    });
  }

  return res.json({
    data: await getFullAccess(req.params.resource),
    accessLevel: "full",
  });
});
```

***

#### How This Stops Real Attack Scenarios

**Scenario 1: Real-Time Phishing Proxy (EvilGinx)**

The attacker sets up a phishing page that proxies requests to your real login page in real-time, capturing both the password and the MFA token as the employee enters them.

**Without Guardian:** Attacker replays the captured session cookie and gets full access.

**With Guardian:** The proxied session has the *employee's* `visitorId` (since the browser was the employee's). But when the attacker uses the stolen session cookie from their own browser, Guardian sees a completely different `visitorId`. The session is invalidated.

**Scenario 2: Credential Stuffing from Breached Databases**

The attacker obtains employee credentials from a third-party data breach (password reuse) and attempts to log in via automated scripts.

**Without Guardian:** If the password works, the attacker gets in.

**With Guardian:** Bot detection fires (automated script), `visitorId` is unknown, likely VPN and VM signals. Login blocked immediately.

**Scenario 3: Insider Sharing Credentials**

An employee shares their login with an unauthorized person (e.g., a subcontractor logging into a system they shouldn't have access to).

**Without Guardian:** The unauthorized person logs in successfully.

**With Guardian:** The unauthorized person's device has a different `visitorId`. They're prompted for device verification, and IT admin is notified of a new device enrollment attempt.

***

#### Best Practices

**Do**

* **Enroll devices during onboarding** — When an employee first sets up their account, register their primary work device as trusted
* **Set expiration policies** — Require re-verification every 30–90 days based on role sensitivity
* **Monitor continuously** — Don't just check at login; verify device trust on sensitive actions
* **Alert IT admins** — Automatically notify when high-risk logins are blocked or new devices are detected
* **Log everything** — Maintain a full audit trail of device enrollments, revocations, and blocked attempts
* **Revoke on termination** — Include device trust revocation in your employee offboarding checklist

**Don't**

* **Don't replace MFA** — Guardian is an additional layer, not a replacement for multi-factor authentication
* **Don't trust VPN signals alone** — Some legitimate employees use VPNs; combine signals for risk scoring
* **Don't block immediately on medium risk** — Challenge first, block only on high-risk combinations
* **Don't forget shared devices** — Kiosks and shared workstations need different policies (tie trust to the device, not the user)
* **Don't expose visitorId to employees** — Keep device identity server-side only

***

#### Conclusion

Phishing is the #1 attack vector for a reason — it works. Passwords get stolen. MFA tokens get intercepted. Session cookies get hijacked. But the one thing an attacker cannot steal through a phishing email is the employee's physical device.

Guardian Stack's device trust layer gives you:

* **Phishing resilience** — Stolen credentials are useless without the trusted device
* **Zero friction for employees** — Recognition is silent; trusted devices log in normally
* **Visibility for IT** — See every device accessing your systems, revoke instantly
* **Role-based enforcement** — Stricter policies for sensitive roles, flexible for general staff
* **Audit trail** — Complete log of every device enrollment, login attempt, and security event

The key insight: **Identity is not just who you are — it's what device you're on.** By making the device an authentication factor, you close the gap that phishing exploits.

***

{% hint style="success" %}
**Get Started:** [Sign up for Guardian Stack](https://dashboard.guardianstack.ai/) and start protecting your employee access today.
{% endhint %}


# Stopping Sybil Attacks at Scale

Mugshot increases fraud detection by 22x and cuts costs by 20% with Guardian. Mugshot suffered sybil attacks in the web3 industry.

<div data-with-frame="true"><figure><img src="https://3773527904-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FaQpbYgAyZEBrDPFZtNaq%2Fuploads%2FSFDtOf6O9fUZzeAJCCyw%2Fimage.png?alt=media&amp;token=50d12cd2-ae38-40c6-a4f4-c4aecf98ecc3" alt=""><figcaption></figcaption></figure></div>

Mugshot is a Web3-native sustainability platform that gamifies the circular economy. By rewarding users with crypto tokens for choosing reusable cups over disposables, Mugshot incentivizes positive environmental habits.

{% hint style="danger" %}
With a direct financial incentive attached to every scanned cup, the platform became an immediate target for sophisticated actors looking to "farm" rewards without actually participating in the ecosystem.
{% endhint %}

### Challenge: The "Crypto Farmer" Problem

As a rewards-based Web3 application, Mugshot faced a unique set of challenges that traditional Web2 apps rarely encounter.

1. Sybil Attacks: Sophisticated "farmers" used emulators and scripted bots to create thousands of fake accounts (Sybil identities) to drain the reward pool.
2. Wallet Churn: Attackers would constantly rotate crypto wallets to bypass basic identity checks.
3. Vendor Fatigue: Mugshot initially deployed a well-known enterprise fingerprinting solution. While effective, the pricing model became prohibitive as the user base scaled.

> We were paying enterprise rates for a solution that was great for e-commerce but didn't fully grasp the nuance of crypto-farming. We were bleeding budget on identity checks while sophisticated bots still slipped through.

### Why GuardianStack was the fix

Mugshot needed a solution that offered higher entropy (accuracy) at a sustainable price point. After evaluating several vendors, they switched to Guardian.

The decision drivers were:

* **Cost Efficiency:** Guardian offered a transparent pricing model that reduced their monthly bill by over 20% compared to their previous vendor.
* **Web3-Ready Signals:** The ability to detect specific browser anomalies common in "farming" setups (headless browsers, injected wallet scripts, and automation tools).
* **Privacy-First:** As a Web3 company, Mugshot values user privacy. Guardian's hashing architecture allowed them to stop fraud without intrusive PII collection.

{% hint style="success" %}
Mugshot saw an immediate **22x increase** in the detection of fraudulent signals compared to their previous legacy provider.
{% endhint %}

### How Mugshot uses Guardian

Mugshot integrated the GuardianStack SDK directly into their Reward Claim and Wallet Connection flows.

Instead of banning users immediately, they used GuardianStack's Visitor ID to flag suspicious devices for "Soft Challenges", requiring additional verification only for high-risk users.

#### Turning signals into intelligence

Mugshot utilized Guardian's raw device signals to identify "clusters" of fraud. When one bad actor was caught, Guardian allowed the team to look back and retroactively ban hundreds of associated wallets that shared the same deep-device parameters, even if they used different IP addresses or VPNs.

> It wasn't just about stopping one bot. Guardian gave us the data to map out entire farming rings. We realized 22x more accounts were fraudulent than we thought.

### The Impact

Since switching to Guardian, Mugshot has secured their token economy, ensuring rewards go to real humans saving the planet, not bot farms.

* **22x Increase in Fraud Detection**: Uncovered hidden bot rings the previous vendor missed.
* **>20% Cost Reduction:** Lowered operational costs, allowing funds to be reinvested into user rewards.
* **Industry leading low false positives**: Legitimate eco-conscious users experienced no friction.

{% hint style="info" %}

#### Ready to stop fraud without breaking the bank?

Get the high-entropy signals Mugshot uses to block farmers and save 20%.

[Get your API Key →](https://www.google.com/search?q=https://dashboard.guardianstack.ai)
{% endhint %}


# Laravel / PHP Integration

Protect a Laravel (or plain PHP) application with Guardian in three short steps.

1. Load the Guardian agent on your frontend and get a `requestId`.
2. Send that `requestId` to your backend.
3. From PHP, fetch the processed event by id and decide what to do.

### 1. Frontend

#### Option A: JavaScript / TypeScript

If your frontend uses a bundler (Vite, webpack, Mix, etc.), install the SDK and call it where it matters.

```bash
npm install @guardianstack/guardian-js
```

```ts
// frontend/guardian.ts
import { loadAgent } from '@guardianstack/guardian-js';

// 1) Initialize once at app startup
const guardian = await loadAgent({
  siteKey: 'site_XXXXXXXX',
});

// 2) Trigger an identification exactly where it matters (login, signup, checkout)
const res = await guardian.get();

// 3) Extract the requestId and send it to your backend for risk evaluation
const requestId = res?.requestId;
```

#### Option B: Blade or plain PHP (no bundler)

If you do not use a JavaScript bundler, load the `@guardianstack/guardian-js` package directly from jsDelivr. It serves the published npm build, no local install required.

```blade
{{-- resources/views/layouts/app.blade.php --}}
<script src="https://cdn.jsdelivr.net/npm/@guardianstack/guardian-js" defer></script>
```

Then, on the page where you want to protect an action:

```html
<script>
  document.getElementById('signup-form').addEventListener('submit', async (e) => {
    e.preventDefault();

    // 1) Initialize the Guardian SDK (loaded via jsDelivr above)
    const guardian = await window.FraudDetectionSDK.loadAgent({
      siteKey: 'site_XXXXXXXX',
    });

    // 2) Trigger identification
    const res = await guardian.get();

    // 3) Forward the requestId to your backend
    const form = e.target;
    const hidden = document.createElement('input');
    hidden.type = 'hidden';
    hidden.name = 'guardian_request_id';
    hidden.value = res?.requestId ?? '';
    form.appendChild(hidden);
    form.submit();
  });
</script>
```

You can pin to an exact version for reproducible builds:

```html
<script src="https://cdn.jsdelivr.net/npm/@guardianstack/guardian-js@0.2.7" defer></script>
```

### 2. Backend (Laravel / PHP)

Your backend receives the `requestId` and calls Guardian to fetch the processed event.

Guardian processes events asynchronously, so the first fetch can return `404` for a moment. Retry a few times with a short delay until the event is ready.

Add your secret to `.env`:

```dotenv
GUARDIAN_SECRET=sec_XXXXXXXX
```

Fetch the event with Laravel's HTTP client:

```php
use Illuminate\Support\Facades\Http;

function getGuardianEvent(string $requestId): array
{
    $secret = config('services.guardian.secret'); // or env('GUARDIAN_SECRET')
    $url    = "https://api.guardianstack.ai/request/event/{$requestId}";

    // Retry a few times while the event is still being processed
    for ($attempt = 0; $attempt < 10; $attempt++) {
        $response = Http::withToken($secret)
            ->acceptJson()
            ->timeout(10)
            ->get($url);

        if ($response->successful()) {
            return $response->json();
        }

        if ($response->status() === 404) {
            usleep(250_000); // 250ms, then retry
            continue;
        }

        // Any other error is final
        abort($response->status(), $response->body());
    }

    abort(504, 'Guardian event not ready');
}
```

Use it in a controller:

```php
public function signup(Request $request)
{
    $event = getGuardianEvent($request->input('guardian_request_id'));

    $isBot       = $event['botDetection']['detected'] ?? false;
    $isVpn       = $event['vpn']['detected']          ?? false;
    $isTampering = $event['tampering']['detected']    ?? false;

    if ($isBot || $isTampering || $isVpn) {
        abort(403, 'Request blocked');
    }

    // proceed with signup
}
```

#### Which signals should you gate on?

The example above blocks on bot, VPN and tampering, but the right combination of signals depends on what you are actually protecting. We publish a dedicated guide for each common use case, with recommended thresholds and decision logic:

* [New account fraud prevention](https://docs.guardianstack.ai/documentation/protect-your-implementation/new-account-fraud-prevention)
* [Payment fraud prevention](https://docs.guardianstack.ai/documentation/protect-your-implementation/payment-fraud-prevention)
* [Account takeover prevention](https://docs.guardianstack.ai/documentation/protect-your-implementation/account-takeover-prevention)
* [Returning user experience](https://docs.guardianstack.ai/documentation/protect-your-implementation/returning-user-experience)
* [Web3 fraud prevention](https://docs.guardianstack.ai/documentation/protect-your-implementation/web3-fraud-prevention)
* [Employee device trust](https://docs.guardianstack.ai/documentation/protect-your-implementation/employee-device-trust)

Pick the guide closest to your use case and adapt the PHP checks above to match its recommendations.

#### Plain PHP (no Laravel)

If you are not using Laravel, the same call with cURL:

```php
function getGuardianEvent(string $requestId): array
{
    $secret = getenv('GUARDIAN_SECRET');
    $url    = "https://api.guardianstack.ai/request/event/{$requestId}";

    for ($attempt = 0; $attempt < 10; $attempt++) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_HTTPHEADER     => [
                "Authorization: Bearer {$secret}",
                'Accept: application/json',
            ],
        ]);

        $body   = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($status >= 200 && $status < 300) {
            return json_decode($body, true);
        }

        if ($status === 404) {
            usleep(250_000);
            continue;
        }

        throw new \RuntimeException("Guardian error {$status}: {$body}");
    }

    throw new \RuntimeException('Guardian event not ready');
}
```

### That's it

* Frontend gets a `requestId` from `guardian.get()`.
* Backend fetches the event with `Authorization: Bearer <secret>`, retrying on `404`.
* Check the detection fields (`botDetection`, `vpn`, `tampering`, etc.) and decide.

Your secret stays on the server. The site key is safe on the frontend.


# Privacy Policy

Last updated April 20, 2026

This Privacy Notice for MUGSHOT LABS INC ('**we**', '**us**', or '**our**'), describes how and why we might access, collect, store, use, and/or share ('**process**') your personal information when you use our services ('**Services**'), including when you:

* Visit our website at <https://guardianstack.ai> or any website of ours that links to this Privacy Notice
* Use Guardian. GuardianStack provides a browser agent and server API that generate device intelligence and risk scores to prevent bots, account abuse, and spam. The agent collects limited technical signals (e.g., user agent, rendering/features, screen/storage) and the server uses IP/network data to compute a pseudonymous visitor ID—solely for security and fraud prevention. No ad personalisation, cross‑site tracking, or sale of data.
* Engage with us in other related ways, including any sales, marketing, or events

**Questions or concerns?** Reading this Privacy Notice will help you understand your privacy rights and choices. We are responsible for making decisions about how your personal information is processed. If you do not agree with our policies and practices, please do not use our Services. If you still have any questions or concerns, please contact us at <support@guardianstack.ai>.

**About this notice.** MUGSHOT LABS INC operates GuardianStack, a device intelligence service. Our Service has two distinct components:

* **The dashboard at guardianstack.ai:** used by our business customers to manage their account, view analytics, and configure the Service.
* **The Guardian agent (JavaScript SDK, mobile SDKs, and server API):** integrated by our business customers into their own websites and applications to detect bots, VPN use, browser tampering, and similar fraud signals from end users.

This notice describes personal information processing in three situations:

1. **When you visit guardianstack.ai or use our dashboard** (as our business customer or a prospective customer): we act as a controller of your personal information, and this notice governs that relationship directly.
2. **When you are an end user visiting a website or application that has integrated the Guardian agent:** the business customer operating that website or application is the controller of your personal information, and we act as a processor on their behalf. The business customer's own privacy notice governs how your data is used. This notice describes the limited processing we carry out on their behalf.
3. **When you correspond with us, apply for a job with us, or interact with our marketing:** we act as a controller.

**SUMMARY OF KEY POINTS**

***This summary provides key points from our Privacy Notice, but you can find out more details about any of these topics by clicking the link following each key point or by using our table of contents below to find the section you are looking for.***

**What personal information do we process?** When you visit, use, or navigate our Services, we may process personal information depending on how you interact with us and the Services, the choices you make, and the products and features you use. Learn more about personal information you disclose to us.

**Do we process any sensitive personal information?** Some of the information may be considered 'special' or 'sensitive' in certain jurisdictions, for example your racial or ethnic origins, sexual orientation, and religious beliefs. We may process sensitive personal information when necessary with your consent or as otherwise permitted by applicable law. Learn more about sensitive information we process.

**Do we collect any information from third parties?** We do not collect any information from third parties.

**How do we process your information?** We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. We process your information only when we have a valid legal reason to do so. Learn more about how we process your information.

**In what situations and with which parties do we share personal information?** We may share information in specific situations and with specific third parties. Learn more about when and with whom we share your personal information.

**How do we keep your information safe?** We have adequate organisational and technical processes and procedures in place to protect your personal information. However, no electronic transmission over the internet or information storage technology can be guaranteed to be 100% secure, so we cannot promise or guarantee that hackers, cybercriminals, or other unauthorised third parties will not be able to defeat our security and improperly collect, access, steal, or modify your information. Learn more about how we keep your information safe.

**What are your rights?** Depending on where you are located geographically, the applicable privacy law may mean you have certain rights regarding your personal information. Learn more about your privacy rights.

**How do you exercise your rights?** The easiest way to exercise your rights is by submitting a data subject access request, or by contacting us. We will consider and act upon any request in accordance with applicable data protection laws.

Want to learn more about what we do with any information we collect? Review the Privacy Notice in full.

**TABLE OF CONTENTS**

&#x20; &#x20;

1\. WHAT INFORMATION DO WE COLLECT?

2\. HOW DO WE PROCESS YOUR INFORMATION?

3\. WHAT LEGAL BASES DO WE RELY ON TO PROCESS YOUR PERSONAL INFORMATION?

4\. WHEN AND WITH WHOM DO WE SHARE YOUR PERSONAL INFORMATION?

5\. HOW DO WE HANDLE YOUR SOCIAL LOGINS?

6\. HOW LONG DO WE KEEP YOUR INFORMATION?

7\. HOW DO WE KEEP YOUR INFORMATION SAFE?

8\. DO WE COLLECT INFORMATION FROM MINORS?

9\. WHAT ARE YOUR PRIVACY RIGHTS?

10\. CONTROLS FOR DO-NOT-TRACK FEATURES

11\. DO UNITED STATES RESIDENTS HAVE SPECIFIC PRIVACY RIGHTS?

12\. DO WE MAKE UPDATES TO THIS NOTICE?

13\. HOW CAN YOU CONTACT US ABOUT THIS NOTICE?

14\. HOW CAN YOU REVIEW, UPDATE, OR DELETE THE DATA WE COLLECT FROM YOU?

**1. WHAT INFORMATION DO WE COLLECT?**

**Personal information you disclose to us**

***In Short:*** *We collect personal information that you provide to us.*

We collect personal information that you voluntarily provide to us when you register on the Services, express an interest in obtaining information about us or our products and Services, when you participate in activities on the Services, or otherwise when you contact us.

The personal information we process depends on which component of our Service is involved and in what capacity:

**Dashboard and business customer accounts (we act as controller).** When you register for or use the guardianstack.ai dashboard, we collect:

* Name, email address, and company details
* Authentication credentials: hashed passwords, or authentication tokens and basic profile information (name, email, profile picture) received from Google or GitHub if you sign in using those providers
* Billing and payment details (handled by Stripe; we do not store card numbers)
* Communications you send us (support requests, feedback)
* API keys issued to you for integrating the Guardian agent

**End users of customer websites and applications (we act as processor on behalf of our business customer).** When an end user interacts with a website or application into which the Guardian agent has been integrated, we process, on behalf of our business customer, technical signals from the end user's browser or mobile device and their network:

* Device and browser signals (such as browser type and version, user-agent, operating system, platform, language, time zone, screen and rendering characteristics)
* Network signals (IP address, IP-derived approximate location, indicators of VPN, proxy, Tor, or datacenter origin)
* Request-level activity (request timing and velocity)
* Derived pseudonymous identifiers (a visitor ID and request ID generated from the above)

We do not link end-user data to identified accounts unless the business customer provides us with an identifier. We do not use end-user data for advertising, cross-context behavioral tracking, or any purpose other than providing the fraud detection service to the business customer who integrated the agent.

**Sensitive Information.** When necessary, with your consent or as otherwise permitted by applicable law, we process the following categories of sensitive information:

**Payment Data.** We may collect data necessary to process your payment if you choose to make purchases, such as your payment instrument number, and the security code associated with your payment instrument. All payment data is handled and stored by Stripe. You may find their privacy notice link(s) here: <https://stripe.com/ae/privacy>.

**Social Media Login Data.** We may provide you with the option to register with us using your existing social media account details, like your Facebook, X, or other social media account. If you choose to register in this way, we will collect certain profile information about you from the social media provider, as described in the section called 'HOW DO WE HANDLE YOUR SOCIAL LOGINS?' below.

All personal information that you provide to us must be true, complete, and accurate, and you must notify us of any changes to such personal information.

**2. HOW DO WE PROCESS YOUR INFORMATION?**

***In Short:*** *We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We process the personal information for the following purposes listed below. We may also process your information for other purposes only with your prior explicit consent.*

**We process your personal information for a variety of reasons, depending on how you interact with our Services, including:**

* **To facilitate account creation and authentication and otherwise manage user accounts.** We may process your information so you can create and log in to your account, as well as keep your account in working order.
* **To respond to user inquiries/offer support to users.** We may process your information to respond to your inquiries and solve any potential issues you might have with the requested service.
* **To fulfil and manage your orders.** We may process your information to fulfil and manage your orders, payments, returns, and exchanges made through the Services.
* **To save or protect an individual's vital interest.** We may process your information when necessary to save or protect an individual’s vital interest, such as to prevent harm.
* **Fraud and abuse prevention .** Use technical browser and network signals to detect bots, VPN/proxy/Tor, multi‑accounting, and automated abuse.
* **Security monitoring and incident response .** Monitor anomalous activity, investigate security alerts, and remediate incidents using pseudonymous telemetry.
* **Service integrity (rate‑limiting and anti‑spam) .** Identify abnormal request patterns and enforce fair‑use controls to keep the service reliable for everyone.
* **Debugging and error diagnostics .** Use limited, pseudonymous technical data to reproduce and fix defects impacting security or availability.
* **Legal compliance and claims .** Retain minimal records needed to comply with law, enforce terms, and establish/defend legal claims.

**3. WHAT LEGAL BASES DO WE RELY ON TO PROCESS YOUR INFORMATION?**

***In Short:*** *We only process your personal information when we believe it is necessary and we have a valid legal reason (i.e. legal basis) to do so under applicable law, like with your consent, to comply with laws, to provide you with services to enter into or fulfil our contractual obligations, to protect your rights, or to fulfil our legitimate business interests.*

***If you are located in the EU or UK, this section applies to you.***

The General Data Protection Regulation (GDPR) and UK GDPR require us to explain the valid legal bases we rely on in order to process your personal information. As such, we may rely on the following legal bases to process your personal information:

* **Consent.** We may process your information if you have given us permission (i.e. consent) to use your personal information for a specific purpose. You can withdraw your consent at any time. Learn more about withdrawing your consent.
* **Performance of a Contract.** We may process your personal information when we believe it is necessary to fulfil our contractual obligations to you, including providing our Services or at your request prior to entering into a contract with you.
* **Legitimate Interests.** We may process your information when we believe it is reasonably necessary to achieve our legitimate business interests and those interests do not outweigh your interests and fundamental rights and freedoms. For example, we may process your personal information for some of the purposes described in order to:
* Protect our service and users from fraud and abuse; prevent financial and reputational harm.
* Maintain the confidentiality, integrity, and availability of our systems.
* Ensure service quality and prevent degradation caused by automated traffic.
* Keep the service functional and reliable for users without using data for advertising.
* Meet regulatory obligations and protect our rights and those of our users.
* **Legal Obligations.** We may process your information where we believe it is necessary for compliance with our legal obligations, such as to cooperate with a law enforcement body or regulatory agency, exercise or defend our legal rights, or disclose your information as evidence in litigation in which we are involved.
* **Vital Interests.** We may process your information where we believe it is necessary to protect your vital interests or the vital interests of a third party, such as situations involving potential threats to the safety of any person.

***If you are located in Canada, this section applies to you.***

We may process your information if you have given us specific permission (i.e. express consent) to use your personal information for a specific purpose, or in situations where your permission can be inferred (i.e. implied consent). You can withdraw your consent at any time.

In some exceptional cases, we may be legally permitted under applicable law to process your information without your consent, including, for example:

* If collection is clearly in the interests of an individual and consent cannot be obtained in a timely way
* For investigations and fraud detection and prevention
* For business transactions provided certain conditions are met
* If it is contained in a witness statement and the collection is necessary to assess, process, or settle an insurance claim
* For identifying injured, ill, or deceased persons and communicating with next of kin
* If we have reasonable grounds to believe an individual has been, is, or may be victim of financial abuse
* If it is reasonable to expect collection and use with consent would compromise the availability or the accuracy of the information and the collection is reasonable for purposes related to investigating a breach of an agreement or a contravention of the laws of Canada or a province
* If disclosure is required to comply with a subpoena, warrant, court order, or rules of the court relating to the production of records
* If it was produced by an individual in the course of their employment, business, or profession and the collection is consistent with the purposes for which the information was produced
* If the collection is solely for journalistic, artistic, or literary purposes
* If the information is publicly available and is specified by the regulations
* We may disclose de-identified information for approved research or statistics projects, subject to ethics oversight and confidentiality commitments

**4. WHEN AND WITH WHOM DO WE SHARE YOUR PERSONAL INFORMATION?**

***In Short:*** *We may share information in specific situations described in this section and/or with the following third parties.*

We may need to share your personal information in the following situations:

* **Business Transfers.** We may share or transfer your information in connection with, or during negotiations of, any merger, sale of company assets, financing, or acquisition of all or a portion of our business to another company.

**5. HOW DO WE HANDLE YOUR SOCIAL LOGINS?**

***In Short:*** *If you choose to register or log in to our Services using a social media account, we may have access to certain information about you.*

Our Services offer you the ability to register and log in using your third-party social media account details (like your Facebook or X logins). Where you choose to do this, we will receive certain profile information about you from your social media provider. The profile information we receive may vary depending on the social media provider concerned, but will often include your name, email address, friends list, and profile picture, as well as other information you choose to make public on such a social media platform.

We will use the information we receive only for the purposes that are described in this Privacy Notice or that are otherwise made clear to you on the relevant Services. Please note that we do not control, and are not responsible for, other uses of your personal information by your third-party social media provider. We recommend that you review their privacy notice to understand how they collect, use, and share your personal information, and how you can set your privacy preferences on their sites and apps.

**6. HOW LONG DO WE KEEP YOUR INFORMATION?**

***In Short:*** *We keep your information for as long as necessary to fulfil the purposes outlined in this Privacy Notice unless otherwise required by law.*

We will only keep your personal information for as long as it is necessary for the purposes set out in this Privacy Notice, unless a longer retention period is required or permitted by law (such as tax, accounting, or other legal requirements). No purpose in this notice will require us keeping your personal information for longer than the period of time in which users have an account with us.

When we have no ongoing legitimate business need to process your personal information, we will either delete or anonymise such information, or, if this is not possible (for example, because your personal information has been stored in backup archives), then we will securely store your personal information and isolate it from any further processing until deletion is possible.

**7. HOW DO WE KEEP YOUR INFORMATION SAFE?**

***In Short:*** *We aim to protect your personal information through a system of organisational and technical security measures.*

We have implemented appropriate and reasonable technical and organisational security measures designed to protect the security of any personal information we process. However, despite our safeguards and efforts to secure your information, no electronic transmission over the Internet or information storage technology can be guaranteed to be 100% secure, so we cannot promise or guarantee that hackers, cybercriminals, or other unauthorised third parties will not be able to defeat our security and improperly collect, access, steal, or modify your information. Although we will do our best to protect your personal information, transmission of personal information to and from our Services is at your own risk. You should only access the Services within a secure environment.

**8. DO WE COLLECT INFORMATION FROM MINORS?**

***In Short:*** *We do not knowingly collect data from or market to children under 18 years of age or the equivalent age as specified by law in your jurisdiction.*

We do not knowingly collect, solicit data from, or market to children under 18 years of age or the equivalent age as specified by law in your jurisdiction, nor do we knowingly sell such personal information. By using the Services, you represent that you are at least 18 or the equivalent age as specified by law in your jurisdiction or that you are the parent or guardian of such a minor and consent to such minor dependent’s use of the Services. If we learn that personal information from users less than 18 years of age or the equivalent age as specified by law in your jurisdiction has been collected, we will deactivate the account and take reasonable measures to promptly delete such data from our records. If you become aware of any data we may have collected from children under age 18 or the equivalent age as specified by law in your jurisdiction, please contact us at <support@guardianstack.ai>.

**9. WHAT ARE YOUR PRIVACY RIGHTS?**

***In Short:*** *Depending on your state of residence in the US or in some regions, such as the European Economic Area (EEA), United Kingdom (UK), Switzerland, and Canada, you have rights that allow you greater access to and control over your personal information. You may review, change, or terminate your account at any time, depending on your country, province, or state of residence.*

In some regions (like the EEA, UK, Switzerland, and Canada), you have certain rights under applicable data protection laws. These may include the right (i) to request access and obtain a copy of your personal information, (ii) to request rectification or erasure; (iii) to restrict the processing of your personal information; (iv) if applicable, to data portability; and (v) not to be subject to automated decision-making. If a decision that produces legal or similarly significant effects is made solely by automated means, we will inform you, explain the main factors, and offer a simple way to request human review. In certain circumstances, you may also have the right to object to the processing of your personal information. You can make such a request by contacting us by using the contact details provided in the section 'HOW CAN YOU CONTACT US ABOUT THIS NOTICE?' below.

We will consider and act upon any request in accordance with applicable data protection laws.

&#x20;

If you are located in the EEA or UK and you believe we are unlawfully processing your personal information, you also have the right to complain to your Member State data protection authority or UK data protection authority.

If you are located in Switzerland, you may contact the Federal Data Protection and Information Commissioner.

**Withdrawing your consent:** If we are relying on your consent to process your personal information, which may be express and/or implied consent depending on the applicable law, you have the right to withdraw your consent at any time. You can withdraw your consent at any time by contacting us by using the contact details provided in the section 'HOW CAN YOU CONTACT US ABOUT THIS NOTICE?' below or updating your preferences.

However, please note that this will not affect the lawfulness of the processing before its withdrawal nor, when applicable law allows, will it affect the processing of your personal information conducted in reliance on lawful processing grounds other than consent.

**Opting out of marketing and promotional communications:** You can unsubscribe from our marketing and promotional communications at any time by clicking on the unsubscribe link in the emails that we send, or by contacting us using the details provided in the section 'HOW CAN YOU CONTACT US ABOUT THIS NOTICE?' below. You will then be removed from the marketing lists. However, we may still communicate with you — for example, to send you service-related messages that are necessary for the administration and use of your account, to respond to service requests, or for other non-marketing purposes.

**Account Information**

If you would at any time like to review or change the information in your account or terminate your account, you can:

* Contact us using the contact information provided.

Upon your request to terminate your account, we will deactivate or delete your account and information from our active databases. However, we may retain some information in our files to prevent fraud, troubleshoot problems, assist with any investigations, enforce our legal terms and/or comply with applicable legal requirements.

If you have questions or comments about your privacy rights, you may email us at <support@guardianstack.ai>.

**10. CONTROLS FOR DO-NOT-TRACK FEATURES**

Most web browsers and some mobile operating systems and mobile applications include a Do-Not-Track ('DNT') feature or setting you can activate to signal your privacy preference not to have data about your online browsing activities monitored and collected. At this stage, no uniform technology standard for recognising and implementing DNT signals has been finalised. As such, we do not currently respond to DNT browser signals or any other mechanism that automatically communicates your choice not to be tracked online. If a standard for online tracking is adopted that we must follow in the future, we will inform you about that practice in a revised version of this Privacy Notice.

California law requires us to let you know how we respond to web browser DNT signals. Because there currently is not an industry or legal standard for recognising or honouring DNT signals, we do not respond to them at this time.

**11. DO UNITED STATES RESIDENTS HAVE SPECIFIC PRIVACY RIGHTS?**

***In Short:*** *If you are a resident of California, Colorado, Connecticut, Delaware, Florida, Indiana, Iowa, Kentucky, Maryland, Minnesota, Montana, Nebraska, New Hampshire, New Jersey, Oregon, Rhode Island, Tennessee, Texas, Utah, or Virginia, you may have the right to request access to and receive details about the personal information we maintain about you and how we have processed it, correct inaccuracies, get a copy of, or delete your personal information. You may also have the right to withdraw your consent to our processing of your personal information. These rights may be limited in some circumstances by applicable law. More information is provided below.*

**Categories of Personal Information We Collect**

The table below shows the categories of personal information we have collected in the past twelve (12) months. The table includes illustrative examples of each category and does not reflect the personal information we collect from you. For a comprehensive inventory of all personal information we process, please refer to the section 'WHAT INFORMATION DO WE COLLECT?'

| **Category**   | **Examples**                                                                                                                                                                                             | **Collected** |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| A. Identifiers | Contact details, such as real name, alias, postal address, telephone or mobile contact number, unique personal identifier, online identifier, Internet Protocol address, email address, and account name | YES           |

|                                                                               |                                                                                                 |    |
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -- |
| B. Personal information as defined in the California Customer Records statute | Name, contact information, education, employment, employment history, and financial information | NO |

|                                                                        |                                                                                                                                                                                 |     |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
| C. Protected classification characteristics under state or federal law | Gender, age, date of birth, race and ethnicity, national origin, marital status, and other demographic data                                                                     | NO  |
| D. Commercial information                                              | Transaction information, purchase history, financial details, and payment information                                                                                           | YES |
| E. Biometric information                                               | Fingerprints and voiceprints                                                                                                                                                    | NO  |
| F. Internet or other similar network activity                          | Browsing history, search history, online behaviour, interest data, and interactions with our and other websites, applications, systems, and advertisements                      | YES |
| G. Geolocation data                                                    | Device location                                                                                                                                                                 | YES |
| H. Audio, electronic, sensory, or similar information                  | Images and audio, video or call recordings created in connection with our business activities                                                                                   | NO  |
| I. Professional or employment-related information                      | Business contact details in order to provide you our Services at a business level or job title, work history, and professional qualifications if you apply for a job with us    | NO  |
| J. Education Information                                               | Student records and directory information                                                                                                                                       | NO  |
| K. Inferences drawn from collected personal information                | Inferences drawn from any of the collected personal information listed above to create a profile or summary about, for example, an individual’s preferences and characteristics | NO  |
| L. Sensitive personal Information                                      | Account login information and debit or credit card numbers                                                                                                                      | YES |

We only collect sensitive personal information, as defined by applicable privacy laws or the purposes allowed by law or with your consent. Sensitive personal information may be used, or disclosed to a service provider or contractor, for additional, specified purposes. You may have the right to limit the use or disclosure of your sensitive personal information. We do not collect or process sensitive personal information for the purpose of inferring characteristics about you.

We may also collect other personal information outside of these categories through instances where you interact with us in person, online, or by phone or mail in the context of:

* Receiving help through our customer support channels;
* Participation in customer surveys or contests; and
* Facilitation in the delivery of our Services and to respond to your inquiries.

We will use and retain the collected personal information as needed to provide the Services or for:

* Category F - As long as the user has an account with us
* Category L - As long as the user has an account with us

**Sources of Personal Information**

Learn more about the sources of personal information we collect in 'WHAT INFORMATION DO WE COLLECT?'

**How We Use and Share Personal Information**

Learn more about how we use your personal information in the section, 'HOW DO WE PROCESS YOUR INFORMATION?'

**Will your information be shared with anyone else?**

We may disclose your personal information with our service providers pursuant to a written contract between us and each service provider. Learn more about how we disclose personal information to in the section, 'WHEN AND WITH WHOM DO WE SHARE YOUR PERSONAL INFORMATION?'

We may use your personal information for our own business purposes, such as for undertaking internal research for technological development and demonstration. This is not considered to be 'selling' of your personal information.

We have not disclosed, sold, or shared any personal information to third parties for a business or commercial purpose in the preceding twelve (12) months. We will not sell or share personal information in the future belonging to website visitors, users, and other consumers.

**Your Rights**

You have rights under certain US state data protection laws. However, these rights are not absolute, and in certain cases, we may decline your request as permitted by law. These rights include:

* **Right to know** whether or not we are processing your personal data
* **Right to access** your personal data
* **Right to correct** inaccuracies in your personal data
* **Right to request** the deletion of your personal data
* **Right to obtain a copy** of the personal data you previously shared with us
* **Right to non-discrimination** for exercising your rights
* **Right to opt out** of the processing of your personal data if it is used for targeted advertising (or sharing as defined under California’s privacy law), the sale of personal data, or profiling in furtherance of decisions that produce legal or similarly significant effects ('profiling')

Depending upon the state where you live, you may also have the following rights:

* Right to access the categories of personal data being processed (as permitted by applicable law, including the privacy law in Minnesota)
* Right to obtain a list of the categories of third parties to which we have disclosed personal data (as permitted by applicable law, including the privacy law in California, Delaware, and Maryland)
* Right to obtain a list of specific third parties to which we have disclosed personal data (as permitted by applicable law, including the privacy law in Minnesota and Oregon)
* Right to obtain a list of third parties to which we have sold personal data (as permitted by applicable law, including the privacy law in Connecticut)
* Right to review, understand, question, and depending on where you live, correct how personal data has been profiled (as permitted by applicable law, including the privacy law in Connecticut and Minnesota)
* Right to limit use and disclosure of sensitive personal data (as permitted by applicable law, including the privacy law in California)
* Right to opt out of the collection of sensitive data and personal data collected through the operation of a voice or facial recognition feature (as permitted by applicable law, including the privacy law in Florida)

**How to Exercise Your Rights**

To exercise these rights, you can contact us by submitting a data subject access request, by emailing us at <support@guardianstack.ai>, or by referring to the contact details at the bottom of this document.

Under certain US state data protection laws, you can designate an authorised agent to make a request on your behalf. We may deny a request from an authorised agent that does not submit proof that they have been validly authorised to act on your behalf in accordance with applicable laws.

**Request Verification**

Upon receiving your request, we will need to verify your identity to determine you are the same person about whom we have the information in our system. We will only use personal information provided in your request to verify your identity or authority to make the request. However, if we cannot verify your identity from the information already maintained by us, we may request that you provide additional information for the purposes of verifying your identity and for security or fraud-prevention purposes.

If you submit the request through an authorised agent, we may need to collect additional information to verify your identity before processing your request and the agent will need to provide a written and signed permission from you to submit such request on your behalf.

**Appeals**

Under certain US state data protection laws, if we decline to take action regarding your request, you may appeal our decision by emailing us at <support@guardianstack.ai>. We will inform you in writing of any action taken or not taken in response to the appeal, including a written explanation of the reasons for the decisions. If your appeal is denied, you may submit a complaint to your state attorney general.

**California 'Shine The Light' Law**

California Civil Code Section 1798.83, also known as the 'Shine The Light' law, permits our users who are California residents to request and obtain from us, once a year and free of charge, information about categories of personal information (if any) we disclosed to third parties for direct marketing purposes and the names and addresses of all third parties with which we shared personal information in the immediately preceding calendar year. If you are a California resident and would like to make such a request, please submit your request in writing to us by using the contact details provided in the section 'HOW CAN YOU CONTACT US ABOUT THIS NOTICE?'

**12. DO WE MAKE UPDATES TO THIS NOTICE?**

***In Short:*** *Yes, we will update this notice as necessary to stay compliant with relevant laws.*

We may update this Privacy Notice from time to time. The updated version will be indicated by an updated 'Revised' date at the top of this Privacy Notice. If we make material changes to this Privacy Notice, we may notify you either by prominently posting a notice of such changes or by directly sending you a notification. We encourage you to review this Privacy Notice frequently to be informed of how we are protecting your information.

**13. HOW CAN YOU CONTACT US ABOUT THIS NOTICE?**

If you have questions or comments about this notice, you may email us at <support@guardianstack.ai> or contact us by post at:

MUGSHOT LABS INC

850 New Burton Road, Suite 201, Dover, Delaware 19904 in the County of Kent.&#x20;

United States of America

**EU and UK data subjects.** We are in the process of designating a representative in the European Union under Article 27 GDPR. In the meantime, EU and UK data subjects may contact us directly at <support@guardianstack.ai> for any privacy-related matter, and we will respond in accordance with applicable data protection laws.

**14. HOW CAN YOU REVIEW, UPDATE, OR DELETE THE DATA WE COLLECT FROM YOU?**

Based on the applicable laws of your country or state of residence in the US, you may have the right to request access to the personal information we collect from you, details about how we have processed it, correct inaccuracies, or delete your personal information. You may also have the right to withdraw your consent to our processing of your personal information. These rights may be limited in some circumstances by applicable law. To request to review, update, or delete your personal information, please fill out and submit a data subject access request.


# Terms of service

Last updated April 20, 2026

We are MUGSHOT LABS INC ("**Company**," "**we**," "**us**," "**our**"), a company registered in Delaware, United States  at 850 New Burton Road, Suite 201, Dover, DE 19904.&#x20;

We operate the website [https://guardianstack.ai](https://guardianstack.ai/) (the "**Site**"), as well as any other related products and services that refer or link to these legal terms (the "**Legal Terms**") (collectively, the "**Services**").

Guardian provides accurate device intelligence and browser fingerprinting services via API and SDKs. Our platform helps developers and businesses identify unique visitors for fraud detection and personalization purposes through our SaaS and hosted dashboard.

You can contact us by email at <support@guardianstack.ai>, or by mail to 850 New Burton Road, Suite 201, Dover, DE 19904, United States.

These Legal Terms constitute a legally binding agreement made between you, whether personally or on behalf of an entity ("**you**"), and MUGSHOT LABS INC, concerning your access to and use of the Services. You agree that by accessing the Services, you have read, understood, and agreed to be bound by all of these Legal Terms. IF YOU DO NOT AGREE WITH ALL OF THESE LEGAL TERMS, THEN YOU ARE EXPRESSLY PROHIBITED FROM USING THE SERVICES AND YOU MUST DISCONTINUE USE IMMEDIATELY.

Supplemental terms and conditions or documents that may be posted on the Services from time to time are hereby expressly incorporated herein by reference. We reserve the right, in our sole discretion, to make changes or modifications to these Legal Terms from time to time. We will alert you about any changes by updating the "Last updated" date of these Legal Terms, and you waive any right to receive specific notice of each such change. It is your responsibility to periodically review these Legal Terms to stay informed of updates. You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Legal Terms by your continued use of the Services after the date such revised Legal Terms are posted.

The Services are intended for users who are at least 18 years old. Persons under the age of 18 are not permitted to use or register for the Services.

We recommend that you print a copy of these Legal Terms for your records.

**TABLE OF CONTENTS**

1\. OUR SERVICES

2\. INTELLECTUAL PROPERTY RIGHTS

3\. USER REPRESENTATIONS

4\. USER REGISTRATION

5\. PURCHASES AND PAYMENT

6\. SUBSCRIPTIONS

7\. SOFTWARE

8\. PROHIBITED ACTIVITIES

9\. USER GENERATED CONTRIBUTIONS

10\. CONTRIBUTION LICENSE

11\. THIRD-PARTY WEBSITES AND CONTENT

12\. SERVICES MANAGEMENT

13\. PRIVACY POLICY

14\. TERM AND TERMINATION

15\. MODIFICATIONS AND INTERRUPTIONS

16\. GOVERNING LAW

17\. DISPUTE RESOLUTION

18\. CORRECTIONS

19\. DISCLAIMER

20\. LIMITATIONS OF LIABILITY

21\. INDEMNIFICATION

22\. USER DATA

23\. ELECTRONIC COMMUNICATIONS, TRANSACTIONS, AND SIGNATURES

24\. CALIFORNIA USERS AND RESIDENTS

25\. MISCELLANEOUS

26\. DATA RIGHTS AND VISITOR DATA LICENSE

27\. CONFIDENTIALITY

28\. CONTACT US

**1. OUR SERVICES**

The information provided when using the Services is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. Accordingly, those persons who choose to access the Services from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable.

The Services are not tailored to comply with industry-specific regulations (Health Insurance Portability and Accountability Act (HIPAA), Federal Information Security Management Act (FISMA), etc.), so if your interactions would be subjected to such laws, you may not use the Services. You may not use the Services in a way that would violate the Gramm-Leach-Bliley Act (GLBA).

**2. INTELLECTUAL PROPERTY RIGHTS**

**Our intellectual property**

We are the owner or the licensee of all intellectual property rights in our Services, including all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics in the Services (collectively, the "Content"), as well as the trademarks, service marks, and logos contained therein (the "Marks").

Our Content and Marks are protected by copyright and trademark laws (and various other intellectual property rights and unfair competition laws) and treaties in the United States and around the world.

The Content and Marks are provided in or through the Services "AS IS" for your internal business purpose only.

**Your use of our Services**

Subject to your compliance with these Legal Terms, including the "PROHIBITED ACTIVITIES" section below, we grant you a non-exclusive, non-transferable, revocable license to:

* access the Services; and
* download or print a copy of any portion of the Content to which you have properly gained access,

solely for your internal business purpose.

Except as set out in this section or elsewhere in our Legal Terms, no part of the Services and no Content or Marks may be copied, reproduced, aggregated, republished, uploaded, posted, publicly displayed, encoded, translated, transmitted, distributed, sold, licensed, or otherwise exploited for any commercial purpose whatsoever, without our express prior written permission.

If you wish to make any use of the Services, Content, or Marks other than as set out in this section or elsewhere in our Legal Terms, please address your request to: <support@guardianstack.ai>. If we ever grant you the permission to post, reproduce, or publicly display any part of our Services or Content, you must identify us as the owners or licensors of the Services, Content, or Marks and ensure that any copyright or proprietary notice appears or is visible on posting, reproducing, or displaying our Content.

We reserve all rights not expressly granted to you in and to the Services, Content, and Marks.

Any breach of these Intellectual Property Rights will constitute a material breach of our Legal Terms and your right to use our Services will terminate immediately.

**Your submissions**

Please review this section and the "PROHIBITED ACTIVITIES" section carefully prior to using our Services to understand the (a) rights you give us and (b) obligations you have when you post or upload any content through the Services.

**Submissions:** By directly sending us any question, comment, suggestion, idea, feedback, or other information about the Services ("Submissions"), you agree to assign to us all intellectual property rights in such Submission. You agree that we shall own this Submission and be entitled to its unrestricted use and dissemination for any lawful purpose, commercial or otherwise, without acknowledgment or compensation to you.

**You are responsible for what you post or upload:** By sending us Submissions through any part of the Services you:

* confirm that you have read and agree with our "PROHIBITED ACTIVITIES" and will not post, send, publish, upload, or transmit through the Services any Submission that is illegal, harassing, hateful, harmful, defamatory, obscene, bullying, abusive, discriminatory, threatening to any person or group, sexually explicit, false, inaccurate, deceitful, or misleading;
* to the extent permissible by applicable law, waive any and all moral rights to any such Submission;
* warrant that any such Submission are original to you or that you have the necessary rights and licenses to submit such Submissions and that you have full authority to grant us the above-mentioned rights in relation to your Submissions; and
* warrant and represent that your Submissions do not constitute confidential information.

You are solely responsible for your Submissions and you expressly agree to reimburse us for any and all losses that we may suffer because of your breach of (a) this section, (b) any third party’s intellectual property rights, or (c) applicable law.

**3. USER REPRESENTATIONS**

By using the Services, you represent and warrant that: (1) all registration information you submit will be true, accurate, current, and complete; (2) you will maintain the accuracy of such information and promptly update such registration information as necessary; (3) you have the legal capacity and you agree to comply with these Legal Terms; (4) you are not a minor in the jurisdiction in which you reside; (5) you will not access the Services through automated or non-human means, whether through a bot, script or otherwise; (6) you will not use the Services for any illegal or unauthorized purpose; and (7) your use of the Services will not violate any applicable law or regulation.

If you provide any information that is untrue, inaccurate, not current, or incomplete, we have the right to suspend or terminate your account and refuse any and all current or future use of the Services (or any portion thereof).

**4. USER REGISTRATION**

You may be required to register to use the Services. You agree to keep your password confidential and will be responsible for all use of your account and password. We reserve the right to remove, reclaim, or change a username you select if we determine, in our sole discretion, that such username is inappropriate, obscene, or otherwise objectionable.

**5. PURCHASES AND PAYMENT**

We accept the following forms of payment:

\-  Visa

\-  American Express

\-  Mastercard

\-  Discover

You agree to provide current, complete, and accurate purchase and account information for all purchases made via the Services. You further agree to promptly update account and payment information, including email address, payment method, and payment card expiration date, so that we can complete your transactions and contact you as needed. Sales tax will be added to the price of purchases as deemed required by us. We may change prices at any time. All payments shall be in US dollars.

You agree to pay all charges at the prices then in effect for your purchases and any applicable shipping fees, and you authorize us to charge your chosen payment provider for any such amounts upon placing your order. We reserve the right to correct any errors or mistakes in pricing, even if we have already requested or received payment.

We reserve the right to refuse any order placed through the Services. We may, in our sole discretion, limit or cancel quantities purchased per person, per household, or per order. These restrictions may include orders placed by or under the same customer account, the same payment method, and/or orders that use the same billing or shipping address. We reserve the right to limit or prohibit orders that, in our sole judgment, appear to be placed by dealers, resellers, or distributors.

**6. SUBSCRIPTIONS**

**Billing and Renewal**

Your subscription will continue and automatically renew unless canceled. You consent to our charging your payment method on a recurring basis without requiring your prior approval for each recurring charge, until such time as you cancel the applicable order. The length of your billing cycle will depend on the type of subscription plan you choose when you subscribed to the Services.

**Free Trial**

We offer a 14-day free trial to new users who register with the Services. The account will not be charged and the subscription will be suspended until upgraded to a paid version at the end of the free trial.

**Cancellation**

All purchases are non-refundable. You can cancel your subscription at any time by logging into your account. Your cancellation will take effect at the end of the current paid term. If you have any questions or are unsatisfied with our Services, please email us at <support@guardianstack.ai>.

**Fee Changes**

We may, from time to time, make changes to the subscription fee and will communicate any price changes to you in accordance with applicable law.

**7. SOFTWARE**

We may include software for use in connection with our Services. If such software is accompanied by an end user license agreement ("EULA"), the terms of the EULA will govern your use of the software. If such software is not accompanied by a EULA, then we grant to you a non-exclusive, revocable, personal, and non-transferable license to use such software solely in connection with our services and in accordance with these Legal Terms. Any software and any related documentation is provided "AS IS" without warranty of any kind, either express or implied, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, or non-infringement. You accept any and all risk arising out of use or performance of any software. You may not reproduce or redistribute any software except in accordance with the EULA or these Legal Terms.

**8. PROHIBITED ACTIVITIES**

You may not access or use the Services for any purpose other than that for which we make the Services available. The Services may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us.

As a user of the Services, you agree not to:

* Systematically retrieve data or other content from the Services to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us.
* Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords.
* Circumvent, disable, or otherwise interfere with security-related features of the Services, including features that prevent or restrict the use or copying of any Content or enforce limitations on the use of the Services and/or the Content contained therein.
* Disparage, tarnish, or otherwise harm, in our opinion, us and/or the Services.
* Use any information obtained from the Services in order to harass, abuse, or harm another person.
* Make improper use of our support services or submit false reports of abuse or misconduct.
* Use the Services in a manner inconsistent with any applicable laws or regulations.
* Engage in unauthorized framing of or linking to the Services.
* Upload or transmit (or attempt to upload or to transmit) viruses, Trojan horses, or other material, including excessive use of capital letters and spamming (continuous posting of repetitive text), that interferes with any party’s uninterrupted use and enjoyment of the Services or modifies, impairs, disrupts, alters, or interferes with the use, features, functions, operation, or maintenance of the Services.
* Engage in any automated use of the system, such as using scripts to send comments or messages, or using any data mining, robots, or similar data gathering and extraction tools.
* Delete the copyright or other proprietary rights notice from any Content.
* Attempt to impersonate another user or person or use the username of another user.
* Upload or transmit (or attempt to upload or to transmit) any material that acts as a passive or active information collection or transmission mechanism, including without limitation, clear graphics interchange formats ("gifs"), 1×1 pixels, web bugs, cookies, or other similar devices (sometimes referred to as "spyware" or "passive collection mechanisms" or "pcms").
* Interfere with, disrupt, or create an undue burden on the Services or the networks or services connected to the Services.
* Harass, annoy, intimidate, or threaten any of our employees or agents engaged in providing any portion of the Services to you.
* Attempt to bypass any measures of the Services designed to prevent or restrict access to the Services, or any portion of the Services.
* Copy or adapt the Services' software, including but not limited to Flash, PHP, HTML, JavaScript, or other code.
* Except as permitted by applicable law, decipher, decompile, disassemble, or reverse engineer any of the software comprising or in any way making up a part of the Services.
* Except as may be the result of standard search engine or Internet browser usage, use, launch, develop, or distribute any automated system, including without limitation, any spider, robot, cheat utility, scraper, or offline reader that accesses the Services, or use or launch any unauthorized script or other software.
* Use a buying agent or purchasing agent to make purchases on the Services.
* Make any unauthorized use of the Services, including collecting usernames and/or email addresses of users by electronic or other means for the purpose of sending unsolicited email, or creating user accounts by automated means or under false pretenses.
* Use the Services as part of any effort to compete with us or otherwise use the Services and/or the Content for any revenue-generating endeavor or commercial enterprise.
* Sell or otherwise transfer your profile.
* Reverse engineer, disassemble, decompile, decode, or otherwise attempt to derive or gain access to the source code of the Service, Agent, or Mobile SDKs.
* Use the Service for any benchmarking purpose or to develop a competing product or service.
* Bypass or ignore instructions that control access to the Service, including attempting to circumvent rate limiting systems or obfuscating the source of traffic sent to the Service.
* Share API keys, access credentials, or dashboard access with any third party outside of your organization.

**9. USER GENERATED CONTRIBUTIONS**

The Services does not offer users to submit or post content. We may provide you with the opportunity to create, submit, post, display, transmit, perform, publish, distribute, or broadcast content and materials to us or on the Services, including but not limited to text, writings, video, audio, photographs, graphics, comments, suggestions, or personal information or other material (collectively, "Contributions"). Contributions may be viewable by other users of the Services and through third-party websites. As such, any Contributions you transmit may be treated in accordance with the Services' Privacy Policy. When you create or make available any Contributions, you thereby represent and warrant that:

* The creation, distribution, transmission, public display, or performance, and the accessing, downloading, or copying of your Contributions do not and will not infringe the proprietary rights, including but not limited to the copyright, patent, trademark, trade secret, or moral rights of any third party.
* You are the creator and owner of or have the necessary licenses, rights, consents, releases, and permissions to use and to authorize us, the Services, and other users of the Services to use your Contributions in any manner contemplated by the Services and these Legal Terms.
* You have the written consent, release, and/or permission of each and every identifiable individual person in your Contributions to use the name or likeness of each and every such identifiable individual person to enable inclusion and use of your Contributions in any manner contemplated by the Services and these Legal Terms.
* Your Contributions are not false, inaccurate, or misleading.
* Your Contributions are not unsolicited or unauthorized advertising, promotional materials, pyramid schemes, chain letters, spam, mass mailings, or other forms of solicitation.
* Your Contributions are not obscene, lewd, lascivious, filthy, violent, harassing, libelous, slanderous, or otherwise objectionable (as determined by us).
* Your Contributions do not ridicule, mock, disparage, intimidate, or abuse anyone.
* Your Contributions are not used to harass or threaten (in the legal sense of those terms) any other person and to promote violence against a specific person or class of people.
* Your Contributions do not violate any applicable law, regulation, or rule.
* Your Contributions do not violate the privacy or publicity rights of any third party.
* Your Contributions do not violate any applicable law concerning child pornography, or otherwise intended to protect the health or well-being of minors.
* Your Contributions do not include any offensive comments that are connected to race, national origin, gender, sexual preference, or physical handicap.
* Your Contributions do not otherwise violate, or link to material that violates, any provision of these Legal Terms, or any applicable law or regulation.

Any use of the Services in violation of the foregoing violates these Legal Terms and may result in, among other things, termination or suspension of your rights to use the Services.

**10. CONTRIBUTION LICENSE**

You and Services agree that we may access, store, process, and use any information and personal data that you provide following the terms of the Privacy Policy and your choices (including settings).

By submitting suggestions or other feedback regarding the Services, you agree that we can use and share such feedback for any purpose without compensation to you.

We do not assert any ownership over your Contributions. You retain full ownership of all of your Contributions and any intellectual property rights or other proprietary rights associated with your Contributions. We are not liable for any statements or representations in your Contributions provided by you in any area on the Services. You are solely responsible for your Contributions to the Services and you expressly agree to exonerate us from any and all responsibility and to refrain from any legal action against us regarding your Contributions.

**11. THIRD-PARTY WEBSITES AND CONTENT**

The Services may contain (or you may be sent via the Site) links to other websites ("Third-Party Websites") as well as articles, photographs, text, graphics, pictures, designs, music, sound, video, information, applications, software, and other content or items belonging to or originating from third parties ("Third-Party Content"). Such Third-Party Websites and Third-Party Content are not investigated, monitored, or checked for accuracy, appropriateness, or completeness by us, and we are not responsible for any Third-Party Websites accessed through the Services or any Third-Party Content posted on, available through, or installed from the Services, including the content, accuracy, offensiveness, opinions, reliability, privacy practices, or other policies of or contained in the Third-Party Websites or the Third-Party Content. Inclusion of, linking to, or permitting the use or installation of any Third-Party Websites or any Third-Party Content does not imply approval or endorsement thereof by us. If you decide to leave the Services and access the Third-Party Websites or to use or install any Third-Party Content, you do so at your own risk, and you should be aware these Legal Terms no longer govern. You should review the applicable terms and policies, including privacy and data gathering practices, of any website to which you navigate from the Services or relating to any applications you use or install from the Services. Any purchases you make through Third-Party Websites will be through other websites and from other companies, and we take no responsibility whatsoever in relation to such purchases which are exclusively between you and the applicable third party. You agree and acknowledge that we do not endorse the products or services offered on Third-Party Websites and you shall hold us blameless from any harm caused by your purchase of such products or services. Additionally, you shall hold us blameless from any losses sustained by you or harm caused to you relating to or resulting in any way from any Third-Party Content or any contact with Third-Party Websites.

**12. SERVICES MANAGEMENT**

We reserve the right, but not the obligation, to: (1) monitor the Services for violations of these Legal Terms; (2) take appropriate legal action against anyone who, in our sole discretion, violates the law or these Legal Terms, including without limitation, reporting such user to law enforcement authorities; (3) in our sole discretion and without limitation, refuse, restrict access to, limit the availability of, or disable (to the extent technologically feasible) any of your Contributions or any portion thereof; (4) in our sole discretion and without limitation, notice, or liability, to remove from the Services or otherwise disable all files and content that are excessive in size or are in any way burdensome to our systems; and (5) otherwise manage the Services in a manner designed to protect our rights and property and to facilitate the proper functioning of the Services.

**13. PRIVACY POLICY**

We care about data privacy and security. Please review our Privacy Policy: [**https://docs.guardianstack.ai/documentation/legal/privacy-policy**](https://docs.guardianstack.ai/documentation/legal/privacy-policy). By using the Services, you agree to be bound by our Privacy Policy, which is incorporated into these Legal Terms. Please be advised the Services are hosted in the United States and Sweden. If you access the Services from any other region of the world with laws or other requirements governing personal data collection, use, or disclosure that differ from applicable laws in the United States and Sweden, then through your continued use of the Services, you are transferring your data to the United States and Sweden, and you expressly consent to have your data transferred to and processed in the United States and Sweden.

**14. TERM AND TERMINATION**

These Legal Terms shall remain in full force and effect while you use the Services. WITHOUT LIMITING ANY OTHER PROVISION OF THESE LEGAL TERMS, WE RESERVE THE RIGHT TO, IN OUR SOLE DISCRETION AND WITHOUT NOTICE OR LIABILITY, DENY ACCESS TO AND USE OF THE SERVICES (INCLUDING BLOCKING CERTAIN IP ADDRESSES), TO ANY PERSON FOR ANY REASON OR FOR NO REASON, INCLUDING WITHOUT LIMITATION FOR BREACH OF ANY REPRESENTATION, WARRANTY, OR COVENANT CONTAINED IN THESE LEGAL TERMS OR OF ANY APPLICABLE LAW OR REGULATION. WE MAY TERMINATE YOUR USE OR PARTICIPATION IN THE SERVICES OR DELETE YOUR ACCOUNT AND ANY CONTENT OR INFORMATION THAT YOU POSTED AT ANY TIME, WITHOUT WARNING, IN OUR SOLE DISCRETION.

If we terminate or suspend your account for any reason, you are prohibited from registering and creating a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party. In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive redress.

**15. MODIFICATIONS AND INTERRUPTIONS**

We reserve the right to change, modify, or remove the contents of the Services at any time or for any reason at our sole discretion without notice. However, we have no obligation to update any information on our Services. We will not be liable to you or any third party for any modification, price change, suspension, or discontinuance of the Services.

We cannot guarantee the Services will be available at all times. We may experience hardware, software, or other problems or need to perform maintenance related to the Services, resulting in interruptions, delays, or errors. We reserve the right to change, revise, update, suspend, discontinue, or otherwise modify the Services at any time or for any reason without notice to you. You agree that we have no liability whatsoever for any loss, damage, or inconvenience caused by your inability to access or use the Services during any downtime or discontinuance of the Services. Nothing in these Legal Terms will be construed to obligate us to maintain and support the Services or to supply any corrections, updates, or releases in connection therewith.

**16. GOVERNING LAW**

These Legal Terms and your use of the Services are governed by and construed in accordance with the laws of the State of Delaware applicable to agreements made and to be entirely performed within the State of Delaware, without regard to its conflict of law principles.

**17. DISPUTE RESOLUTION**

Any legal action of whatever nature brought by either you or us (collectively, the "Parties" and individually, a "Party") shall be commenced or prosecuted in the state and federal courts located in Delaware, and the Parties hereby consent to, and waive all defenses of lack of personal jurisdiction and forum non conveniens with respect to venue and jurisdiction in such state and federal courts. Application of the United Nations Convention on Contracts for the International Sale of Goods and the Uniform Computer Information Transaction Act (UCITA) are excluded from these Legal Terms.&#x20;

**18. CORRECTIONS**

There may be information on the Services that contains typographical errors, inaccuracies, or omissions, including descriptions, pricing, availability, and various other information. We reserve the right to correct any errors, inaccuracies, or omissions and to change or update the information on the Services at any time, without prior notice.

**19. DISCLAIMER**

THE SERVICES ARE PROVIDED ON AN AS-IS AND AS-AVAILABLE BASIS. YOU AGREE THAT YOUR USE OF THE SERVICES WILL BE AT YOUR SOLE RISK. TO THE FULLEST EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, IN CONNECTION WITH THE SERVICES AND YOUR USE THEREOF, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE MAKE NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE SERVICES' CONTENT OR THE CONTENT OF ANY WEBSITES OR MOBILE APPLICATIONS LINKED TO THE SERVICES AND WE WILL ASSUME NO LIABILITY OR RESPONSIBILITY FOR ANY (1) ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (2) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO AND USE OF THE SERVICES, (3) ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL INFORMATION AND/OR FINANCIAL INFORMATION STORED THEREIN, (4) ANY INTERRUPTION OR CESSATION OF TRANSMISSION TO OR FROM THE SERVICES, (5) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE SERVICES BY ANY THIRD PARTY, AND/OR (6) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE SERVICES. WE DO NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE SERVICES, ANY HYPERLINKED WEBSITE, OR ANY WEBSITE OR MOBILE APPLICATION FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND WE WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND ANY THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES. AS WITH THE PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE.

**20. LIMITATIONS OF LIABILITY**

IN NO EVENT WILL WE OR OUR DIRECTORS, EMPLOYEES, OR AGENTS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, CONSEQUENTIAL, EXEMPLARY, INCIDENTAL, SPECIAL, OR PUNITIVE DAMAGES, INCLUDING LOST PROFIT, LOST REVENUE, LOSS OF DATA, OR OTHER DAMAGES ARISING FROM YOUR USE OF THE SERVICES, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, OUR LIABILITY TO YOU FOR ANY CAUSE WHATSOEVER AND REGARDLESS OF THE FORM OF THE ACTION, WILL AT ALL TIMES BE LIMITED TO THE AMOUNT PAID, IF ANY, BY YOU TO US DURING THE twelve (12) mONTH PERIOD PRIOR TO ANY CAUSE OF ACTION ARISING. CERTAIN US STATE LAWS AND INTERNATIONAL LAWS DO NOT ALLOW LIMITATIONS ON IMPLIED WARRANTIES OR THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES. IF THESE LAWS APPLY TO YOU, SOME OR ALL OF THE ABOVE DISCLAIMERS OR LIMITATIONS MAY NOT APPLY TO YOU, AND YOU MAY HAVE ADDITIONAL RIGHTS.

**21. INDEMNIFICATION**

You agree to defend, indemnify, and hold us harmless, including our subsidiaries, affiliates, and all of our respective officers, agents, partners, and employees, from and against any loss, damage, liability, claim, or demand, including reasonable attorneys’ fees and expenses, made by any third party due to or arising out of: (1) use of the Services; (2) breach of these Legal Terms; (3) any breach of your representations and warranties set forth in these Legal Terms; (4) your violation of the rights of a third party, including but not limited to intellectual property rights; or (5) any overt harmful act toward any other user of the Services with whom you connected via the Services. Notwithstanding the foregoing, we reserve the right, at your expense, to assume the exclusive defense and control of any matter for which you are required to indemnify us, and you agree to cooperate, at your expense, with our defense of such claims. We will use reasonable efforts to notify you of any such claim, action, or proceeding which is subject to this indemnification upon becoming aware of it.

**22. USER DATA**

We will maintain certain data that you transmit to the Services for the purpose of managing the performance of the Services, as well as data relating to your use of the Services. Although we perform regular routine backups of data, you are solely responsible for all data that you transmit or that relates to any activity you have undertaken using the Services. You agree that we shall have no liability to you for any loss or corruption of any such data, and you hereby waive any right of action against us arising from any such loss or corruption of such data.

**23. ELECTRONIC COMMUNICATIONS, TRANSACTIONS, AND SIGNATURES**

Visiting the Services, sending us emails, and completing online forms constitute electronic communications. You consent to receive electronic communications, and you agree that all agreements, notices, disclosures, and other communications we provide to you electronically, via email and on the Services, satisfy any legal requirement that such communication be in writing. YOU HEREBY AGREE TO THE USE OF ELECTRONIC SIGNATURES, CONTRACTS, ORDERS, AND OTHER RECORDS, AND TO ELECTRONIC DELIVERY OF NOTICES, POLICIES, AND RECORDS OF TRANSACTIONS INITIATED OR COMPLETED BY US OR VIA THE SERVICES. You hereby waive any rights or requirements under any statutes, regulations, rules, ordinances, or other laws in any jurisdiction which require an original signature or delivery or retention of non-electronic records, or to payments or the granting of credits by any means other than electronic means.

**24. CALIFORNIA USERS AND RESIDENTS**

If any complaint with us is not satisfactorily resolved, you can contact the Complaint Assistance Unit of the Division of Consumer Services of the California Department of Consumer Affairs in writing at 1625 North Market Blvd., Suite N 112, Sacramento, California 95834 or by telephone at (800) 952-5210 or (916) 445-1254.

**25. MISCELLANEOUS**

These Legal Terms and any policies or operating rules posted by us on the Services or in respect to the Services constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Legal Terms shall not operate as a waiver of such right or provision. These Legal Terms operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time. We shall not be responsible or liable for any loss, damage, delay, or failure to act caused by any cause beyond our reasonable control. If any provision or part of a provision of these Legal Terms is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Legal Terms and does not affect the validity and enforceability of any remaining provisions. There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Legal Terms or use of the Services. You agree that these Legal Terms will not be construed against us by virtue of having drafted them. You hereby waive any and all defenses you may have based on the electronic form of these Legal Terms and the lack of signing by the parties hereto to execute these Legal Terms.

**26. DATA RIGHTS AND VISITOR DATA LICENSE**

License Grant. Customer grants Guardian a non-exclusive, worldwide, royalty-free, non-transferable right and license to: (i) use, copy, store, transmit, display, modify, and create derivative works of Visitor Data as necessary to provide the Service to Customer; and (ii) aggregate the Visitor Data with other data in a de-identified form and use such derived data for any lawful purpose (including improving the Service, analytics, and benchmarking) both during and after the Subscription Term. Ownership. Customer retains all intellectual property and other rights in the Visitor Data provided to Guardian, except for the limited usage rights granted in this Agreement. Guardian retains all rights in the Service, the Agent, the Mobile SDKs, and any derived data or improvements to the Service generated by Guardian. Data Compliance. Customer represents and warrants that it has made all necessary disclosures and obtained all necessary consents required by Applicable Laws (including GDPR and CCPA) for its submission of Visitor Data to Guardian.

**27. CONFIDENTIALITY**

"Confidential Information" means information disclosed by one Party to the other that is designated as proprietary or confidential or that should be reasonably understood to be proprietary or confidential due to its nature and the circumstances of its disclosure. Our Confidential Information specifically includes any technical or performance information about the Services, specific pricing terms (if not publicly listed), and all non-public aspects of the Services.

**Obligations**. As the receiving party, each party will: (a) hold in confidence and not disclose Confidential Information to third parties except as permitted in these Legal Terms; and (b) only use Confidential Information to fulfill its obligations and exercise its rights in these Legal Terms. The receiving party may disclose Confidential Information to its employees, agents, contractors, and other representatives having a legitimate need to know, provided it remains responsible for their compliance with this section.

**Exceptions**. These confidentiality obligations do not apply to information that the receiving party can document: (a) is or becomes public knowledge through no fault of the receiving party; (b) it rightfully knew or possessed prior to receipt; (c) it rightfully received from a third party without breach of confidentiality obligations; or (d) it independently developed without using the disclosing party’s Confidential Information.

**Remedies**. Unauthorized use or disclosure of Confidential Information may cause substantial harm for which damages alone are an insufficient remedy. Each party may seek appropriate equitable relief, in addition to other available remedies, for breach or threatened breach of this section.

**28. CONTACT US**

In order to resolve a complaint regarding the Services or to receive further information regarding use of the Services, please contact us at:

**MUGSHOT LABS INC**

**850 New Burton Road, Suite 201**

**Dover, DE 19904**

**United States**

[**support@guardianstack.ai**](mailto:support@guardianstack.ai)


# DPA

Last Updated: December 28, 2025

This Data Processing Agreement (“DPA”) forms part of the Terms of Service (“Agreement”) between MUGSHOT LABS INC, a Delaware corporation (“Company”) and the Customer.

By accepting the Terms of Service, or by accessing or using the Company’s services (marketed as GuardianStack), the Customer is deemed to have signed and accepted the terms of this DPA.

### 1. Definitions

* “Affiliate” means an entity that directly or indirectly Controls, is Controlled by or is under common Control with an entity.
* “CCPA” means the California Consumer Privacy Act, as amended by the CPRA.1
* “Controller”, “Processor”, “Data Subject”, “Personal Data”, “Processing” (and “process”), and “Supervisory Authority” have the meanings given to them in the GDPR.
* “Customer Personal Data” means any Personal Data that Company processes in the course of providing the Service to Customer.
* “Data Protection Laws” means all data protection and privacy laws applicable to the processing of Personal Data under the Agreement, including the EU GDPR, UK GDPR, Swiss FADP, the CCPA, and applicable US State Privacy Laws (including but not limited to those of Virginia, Colorado, Connecticut, and Utah).
* “EU GDPR” means Regulation (EU) 2016/679.2
* “Service” means the device intelligence and browser fingerprinting services provided by Company via API and SDKs.
* “Security Incident” means any breach of security that leads to the accidental or unlawful destruction, loss, alteration, unauthorized disclosure of or access to Customer Personal Data.
* “Standard Contractual Clauses” (SCCs) means the clauses annexed to Commission Implementing Decision (EU) 2021/914.3
* “Sub-processor” means any Processor engaged by Company to assist in fulfilling its obligations with respect to providing the Service.

### 2. Roles and Scope

2.1 Role of the Parties.

Except as set out in Section 2.2, the Parties agree that Customer is the Controller of Customer Personal Data and Company shall process Customer Personal Data only as a Processor on behalf of Customer.

2.2 Company as Controller.

The Parties acknowledge that Company collects and processes certain data (Administration Data and Usage Data) as a Controller for legitimate business purposes, including billing, account management, and product development/model improvement (the “Controller Purposes”).

2.3 Processing Instructions.

Company shall process Customer Personal Data only for the purposes of providing the Service in accordance with the Agreement, unless required to do otherwise by applicable law.

2.4 California (CCPA) and US State Law Designation.

To the extent the CCPA or similar US State Privacy Laws apply, Company acts as a “Service Provider” (or "Processor"). Company shall not (a) sell or share Customer Personal Data; (b) retain, use, or disclose Customer Personal Data for any purpose other than for the specific purpose of performing the Services; or (c) combine Customer Personal Data with personal data received from other sources, except as permitted by applicable law.

### 3. Sub-processing

3.1 Authorization.

Customer grants Company general authorization to engage Sub-processors to process Customer Personal Data. The current Sub-processors are listed in Schedule 4.

3.2 Changes to Sub-processors.

Company shall provide Customer with notice (via email or in-app notification) of any intended changes concerning the addition or replacement of Sub-processors. Customer may object to such changes within ten (10) days. If the Parties cannot resolve the objection, either Party may terminate the affected Service. Termination shall be the Customer’s sole and exclusive remedy with respect to such objection.

3.3 Obligations.

Company shall enter into a written agreement with each Sub-processor imposing data protection obligations no less protective than those set out in this DPA.

### 4. Security and Audits

4.1 Security Measures.

Company shall implement and maintain appropriate technical and organizational security measures to protect Customer Personal Data, as described in Schedule 3.

4.2 Confidentiality.

Company shall ensure that personnel authorized to process Customer Personal Data are subject to a duty of confidentiality.

4.3 Security Incidents.

Company will notify Customer without undue delay after becoming aware of a confirmed Security Incident. Such notification shall not be construed as an acknowledgement of fault or liability.

4.4 Audits and Demonstrating Compliance.

Upon written request, Company shall make available to Customer information reasonably necessary to demonstrate compliance with this DPA. This may include:

(a) Completing a written security questionnaire provided by Customer;

(b) Providing certificates of compliance from its hosting providers (e.g., AWS/GCP SOC 2 reports); or

(c) Providing a summary of its most recent internal security review.

If the Customer requires an on-site audit or an audit by a third party, it shall be conducted at Customer’s sole expense, during normal business hours, no more than once per year, and in a manner that does not disrupt Company's business operations.

### 5. International Transfers (SCCs)

5.1 Application of SCCs.

To the extent that the processing involves a transfer of Personal Data to a country outside the European Economic Area (EEA), the UK, or Switzerland that has not been recognized as providing an adequate level of protection:

(a) Module Two (Controller to Processor) of the SCCs shall apply to the provision of the Service.

(b) Module One (Controller to Controller) of the SCCs shall apply to processing for Controller Purposes.

5.2 UK and Swiss Addenda.

For transfers subject to the UK GDPR, the UK Addendum to the EU SCCs shall apply. For transfers subject to Swiss Data Protection Laws, the SCCs shall apply with the necessary modifications to ensure compliance with the Swiss FADP.

<br>

### 6. Cooperation and Data Subject Rights

6.1 Data Subject Requests.

To the extent Customer cannot independently access the relevant data, Company shall (at Customer’s expense) provide reasonable cooperation to assist Customer in responding to any requests from individuals or applicable data protection authorities relating to the processing of Customer Personal Data (e.g., requests for deletion or access).

6.2 Impact Assessments.

Company shall provide reasonable assistance to Customer with any data protection impact assessments (DPIAs) required under Data Protection Laws, taking into account the nature of processing and the information available to Company.

### 7. Return or Deletion of Data

Upon termination or expiration of the Agreement, Company shall delete all Customer Personal Data processed on behalf of Customer, except to the extent that:

(a) The data is being processed for the Controller Purposes defined in Section 2.2 (e.g., fraud modeling and product improvement);

(b) Company is required by applicable law to retain some or all of the data; or

(c) The data is archived on backup systems (which shall be securely isolated until deleted in the normal backup cycle).

### 8. General

8.1 Conflict.

In the event of a conflict between the Agreement and this DPA, this DPA shall prevail. In the event of a conflict between this DPA and the SCCs, the SCCs shall prevail.

8.2 Governing Law.

Except where otherwise required by the SCCs (which shall be governed by the laws of Ireland), this DPA shall be governed by the laws of the State of Delaware, United States.

8.3 Liability.

Each Party’s liability for any breach of this DPA shall be subject to the exclusions and limitations of liability set forth in the Agreement. In no event shall Company’s liability under this DPA exceed the liability caps agreed upon in the Agreement.

#### Schedule 1: Parties

Data Exporter:

* Name: The Customer (as defined in the Agreement)
* Role: Controller
* Activities: Use of the Service to detect fraud.

Data Importer:

* Legal Name: MUGSHOT LABS INC
* Address: 850 New Burton Road, Suite 201, Dover, DE 19904, United States
* Contact: <support@guardianstack.ai>
* Role: Processor (and Controller for Usage/Admin data)
* Activities: Provision of device intelligence and browser fingerprinting services.

#### Schedule 2: Details of Processing

1\. Categories of Data Subjects

* End Users: Individuals visiting the Customer’s websites or applications.
* Authorized Users: Customer employees accessing the Company’s dashboard.

2\. Categories of Personal Data

* End User Data: Device identifiers, IP address, browser configuration, operating system details, geolocation data (approximate), and behavioral signals used for fraud detection.
* Authorized User Data: Name, email address, login credentials, and billing information.

3\. Sensitive Data

* Company does not intentionally collect or process special categories of data (e.g., health, race, biometric ID for identification) unless explicitly configured by the Customer.

4\. Frequency and Duration

* Frequency: Continuous basis.
* Duration: For the term of the Agreement plus the period required for backup deletion or legal compliance.

5\. Nature and Purpose of Processing

* Collecting, storing, and analyzing device and network signals to identify fraudulent behavior, bots, and account takeovers.
* Billing, account management, and service optimization.

#### Schedule 3: Technical and Organizational Security Measures

Company implements the following technical and organizational measures to ensure the security of processing:

1\. Cloud Infrastructure Security

* Company utilizes top-tier cloud service providers (e.g., AWS, Google Cloud) that maintain industry-standard security certifications, including SOC 2 Type II and ISO 27001.
* Physical security of the data centers is managed entirely by these providers, ensuring strict access control, video surveillance, and environmental protections.

2\. Encryption

* In Transit: All data transmitted between the Customer and Company, and between Company’s internal services, is encrypted using TLS 1.2 or higher.
* At Rest: All Customer Personal Data stored in databases and backups is encrypted at rest using AES-256 standards or equivalent.

3\. Access Control

* Least Privilege: Access to production data is restricted to a limited number of authorized personnel on a strict need-to-know basis.4\ <br>
* Authentication: Multi-Factor Authentication (MFA) is enforced for all employees accessing internal systems, cloud infrastructure, and administrative dashboards.
* Offboarding: Access rights are immediately revoked upon termination of employment or change in role.

4\. Software Development Lifecycle (SDLC)

* Code Review: All changes to the codebase undergo peer review prior to deployment to production.
* Separate Environments: Development, testing, and production environments are logically segregated. Customer data is processed only in the production environment.
* Vulnerability Scanning: Company utilizes automated tools to scan code dependencies and infrastructure for known vulnerabilities.

5\. Incident Management

* Company maintains a security incident response process to detect, investigate, and mitigate security events.
* In the event of a confirmed data breach affecting Customer Personal Data, Company will notify the Customer without undue delay.

6\. Personnel Security

* All employees and contractors with access to Customer Personal Data are required to sign confidentiality agreements.
* Regular security awareness training is provided to employees to ensure understanding of data protection best practices.

7\. Business Continuity

* Backups: Database backups are performed daily to ensure data availability in the event of a system failure.
* Retention: Backups are retained for a limited period (e.g., 30 days) and are encrypted to protect against unauthorized access.

#### Schedule 4: List of Sub-processors

The Customer authorizes the following Sub-processors:

| Name of Sub-processor     | Processing Activity            | Location of Data |
| ------------------------- | ------------------------------ | ---------------- |
| Amazon Web Services (AWS) | Cloud Infrastructure & Hosting | USA, Europe      |
| Stripe                    | Payment Processing             | USA              |
| Supabase                  | Auth                           | USA              |
| Netlify                   | Web Hosting                    | USA              |

The Customer may request the full list of current Sub-processors at any time by contacting <support@guardianstack.ai>.

#### Schedule 5: Standard Contractual Clauses (SCCs)

1\. Modules Applied:

* Module One (Controller to Controller): Applies to Administration Data and Usage Data where Company acts as a Controller.
* Module Two (Controller to Processor): Applies to End User Data processed for the Service.

2\. Docking Clause:

* Clause 7 (Docking Clause) shall not apply.

3\. Choice of Forum and Jurisdiction (Clause 17 & 18):

* Governing Law: The laws of Ireland.
* Competent Courts: The courts of Dublin, Ireland.

4\. Competent Supervisory Authority:

* The Data Protection Commission of Ireland shall act as the competent supervisory authority.


# Guardian Server API

Guardian Server API enables you to get more information about your visitors or about individual detection events.

Server API enables you to get more information about individual detection events (using the [`/events`](/api-reference/reference/events) endpoint). Server API must be used only from the server side; it was never designed to be and must not be used from the client side (i.e. browsers, mobile devices).

Server API requests are not billed and do not count towards your monthly allowance.

The following Server APIs are available:

* [`/request/event/:event_id`](/api-reference/reference/events)
  * GET a detailed payload for a **single** event defined by an `event_id`

#### See Also

* To identify the browsers that visit your web application, see our documentation for [Getting Started](https://docs.guardianstack.ai/documentation/).

### Regions

The server API is available in the **Global**, **EU** and **Asia (Mumbai)** regions:

| Region | Base URL                                         | Server Location |
| ------ | ------------------------------------------------ | --------------- |
| Global | `https://api.guardianstack.ai/request/event/{id` | Global          |

### Server API SDKs

For a smoother developer experience, we offer typed SDKs for these languages:

* [Node SDK](https://www.npmjs.com/package/@mugshotlabs/guardianjs-server)

### &#x20;Trying it out

You can try calling the Server API directly from this reference:

1. You are going to need a Secret API Key. You can create one in your Guardian **Dashboard** > [**API Keys**](https://dashboard.guardianstack.ai/keys).
2. To make a request, you will need a `requestId` of an identification event associated with your workspace. Go to **Dashboard** > [**Identification**](https://dashboard.guardianstack.ai/identification) to see your identification events.
3. Scroll down to one of the endpoints, for example, [Get visits by requestId.](/api-reference/reference/events)
4. Set **Authentication** to your secret API key.
5. Set the **id** path parameter to some `requestId` from your dashboard.
6. Make sure the **Base URL** corresponds to the region.
7. Click **Try it!**

A real API response will appear in the **Response** section. Alternatively, you can view the prepared response examples there.


# Events

Retrieve processed events for server-side fraud/risk decisions.

## Get processed event by id

> Returns a processed view of an event for fraud / risk evaluation.\
> \
> \*\*Authentication\*\*\
> Pass your secret key as a Bearer token: \`Authorization: Bearer \<secret>\`.\
> Secrets must not be sent as query parameters — they would be rejected by the upstream WAF\
> and exposed in access logs.\
> \
> \*\*Event availability / eventual consistency\*\*\
> Events are processed asynchronously; the event may temporarily return \`404\` shortly after creation.\
> The official Server SDK retries \`404\` for a short time window.<br>

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"tags":[{"name":"Events","description":"Retrieve processed events for server-side fraud/risk decisions."}],"servers":[{"url":"https://api.guardianstack.ai","description":"Production (default base URL used by the Server SDK)"},{"url":"http://localhost:3000","description":"Local development"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"token","description":"Use your Guardian **secret key** as a Bearer token.\n\nExample: `Authorization: Bearer sec_xxx`\n\nDo not pass the secret as a query parameter (`?secret=...`) — such requests are blocked\nby the upstream WAF and would leak credentials into access logs and referrers.\n"}},"schemas":{"ProcessedEventResponse":{"type":"object","required":["identification"],"properties":{"identification":{"$ref":"#/components/schemas/ProcessedEventIdentification"},"ipInfo":{"allOf":[{"$ref":"#/components/schemas/IpApiIsResponse"}],"nullable":true,"description":"IP intelligence payload used during processing.\nNote: some fields (e.g. `company.whois`, `asn.whois`) may be sanitized server-side.\n"},"vpn":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVpnSummary"}],"nullable":true},"velocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVelocity"}],"nullable":true,"description":"Event-level request velocity (counts) over sliding windows."},"visitorVelocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedVisitorVelocity"}],"nullable":true,"description":"Visitor-level request velocity (counts) over sliding windows."},"botDetection":{"allOf":[{"$ref":"#/components/schemas/BotDetectionSummary"}],"nullable":true},"tampering":{"allOf":[{"$ref":"#/components/schemas/TamperingSummary"}],"nullable":true},"privacySettings":{"allOf":[{"$ref":"#/components/schemas/PrivacySettingsSummary"}],"nullable":true},"virtualization":{"allOf":[{"$ref":"#/components/schemas/VirtualizationSummary"}],"nullable":true},"incognito":{"allOf":[{"$ref":"#/components/schemas/IncognitoSummary"}],"nullable":true}}},"ProcessedEventIdentification":{"type":"object","required":["id","ip"],"properties":{"id":{"type":"string","description":"Guardian event identifier."},"visitorId":{"type":"string","nullable":true,"description":"Stable, server-issued identifier for the visitor (site-scoped)."},"ip":{"type":"string","description":"Server-observed client IP address for the event."},"timestamp":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of event creation."},"url":{"type":"string","nullable":true,"description":"Page URL as reported by the client during collection."},"location":{"allOf":[{"$ref":"#/components/schemas/LocationInfo"}],"nullable":true},"browser":{"$ref":"#/components/schemas/BrowserInfo"}}},"LocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true,"description":"Continent code (e.g. `EU`, `AS`, `NA`)."},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"Local time at the IP-derived location (ISO string)."},"is_dst":{"type":"boolean","nullable":true}}},"BrowserInfo":{"type":"object","properties":{"browserName":{"type":"string","nullable":true},"browserMajorVersion":{"type":"string","nullable":true},"browserFullVersion":{"type":"string","nullable":true},"platform":{"type":"string","nullable":true,"description":"Normalized platform label (e.g. `windows`, `mac`, `ios`, `android`, `linux`, `chromeos`)."},"os":{"type":"string","nullable":true},"osVersion":{"type":"string","nullable":true},"device":{"type":"string","nullable":true},"userAgent":{"type":"string","nullable":true}}},"IpApiIsResponse":{"type":"object","required":["ip","is_bogon","is_mobile","is_satellite","is_crawler","is_datacenter","is_tor","is_proxy","is_vpn","is_abuser"],"properties":{"ip":{"type":"string"},"rir":{"type":"string","nullable":true,"description":"Regional Internet Registry (e.g. `RIPE`, `ARIN`)."},"is_bogon":{"type":"boolean"},"is_mobile":{"type":"boolean"},"is_satellite":{"type":"boolean"},"is_crawler":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_vpn":{"type":"boolean"},"is_abuser":{"type":"boolean"},"vpn":{"allOf":[{"$ref":"#/components/schemas/IpApiVpnInfo"}],"nullable":true},"datacenter":{"allOf":[{"$ref":"#/components/schemas/IpApiDatacenterInfo"}],"nullable":true},"company":{"allOf":[{"$ref":"#/components/schemas/IpApiCompanyInfo"}],"nullable":true},"abuse":{"allOf":[{"$ref":"#/components/schemas/IpApiAbuseContact"}],"nullable":true},"asn":{"allOf":[{"$ref":"#/components/schemas/IpApiAsnInfo"}],"nullable":true},"location":{"allOf":[{"$ref":"#/components/schemas/IpApiLocationInfo"}],"nullable":true},"elapsed_ms":{"type":"number","nullable":true,"description":"Upstream lookup latency in milliseconds (when available)."}}},"IpApiVpnInfo":{"type":"object","required":["ip","service","url","type","last_seen","last_seen_str"],"properties":{"ip":{"type":"string"},"service":{"type":"string"},"url":{"type":"string"},"type":{"type":"string","description":"Category (e.g. `vpn_server`)."},"last_seen":{"type":"number","description":"Epoch milliseconds."},"last_seen_str":{"type":"string","description":"ISO timestamp."},"exit_node_region":{"type":"string","nullable":true}}},"IpApiDatacenterInfo":{"type":"object","required":["datacenter","domain","network"],"properties":{"datacenter":{"type":"string"},"domain":{"type":"string"},"network":{"type":"string"}}},"IpApiCompanyInfo":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"abuser_score":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"description":"Organization type (e.g. `hosting`)."},"network":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiAbuseContact":{"type":"object","properties":{"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}},"IpApiAsnInfo":{"type":"object","required":["asn"],"properties":{"asn":{"type":"number"},"abuser_score":{"type":"string","nullable":true},"route":{"type":"string","nullable":true},"descr":{"type":"string","nullable":true},"country":{"type":"string","nullable":true,"description":"Lowercase country code."},"active":{"type":"boolean","nullable":true},"org":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"abuse":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"created":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"updated":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"rir":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiLocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"ISO string."},"local_time_unix":{"type":"number","nullable":true,"description":"Epoch seconds."},"is_dst":{"type":"boolean","nullable":true}}},"ProcessedEventVpnSummary":{"type":"object","required":["detected"],"properties":{"detected":{"type":"boolean","description":"Whether VPN/proxy usage is detected."},"confidence":{"type":"string","nullable":true,"description":"Confidence label (when available)."},"reason":{"type":"string","nullable":true,"description":"Reason/category for the VPN decision (when available)."},"browserTimezone":{"type":"string","nullable":true,"description":"Timezone reported by the browser/client signals."},"ipTimezone":{"type":"string","nullable":true,"description":"Timezone derived from IP intelligence."},"timezoneDifference":{"type":"number","nullable":true,"description":"Difference between browser and IP timezones in minutes (when available)."}}},"ProcessedEventVelocity":{"type":"object","required":["5m","1h","24h"],"properties":{"5m":{"type":"number","description":"Count in the last 5 minutes."},"1h":{"type":"number","description":"Count in the last 1 hour."},"24h":{"type":"number","description":"Count in the last 24 hours."}}},"ProcessedVisitorVelocity":{"type":"object","required":["5m","1h","24h","7d"],"properties":{"5m":{"type":"number"},"1h":{"type":"number"},"24h":{"type":"number"},"7d":{"type":"number"}}},"BotDetectionSummary":{"type":"object","required":["detected","score","automationSignalsPresent","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number","description":"Numeric score (higher generally indicates higher likelihood)."},"automationSignalsPresent":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}},"TamperingSummary":{"type":"object","required":["detected","anomalyScore","antiDetectBrowser","indicators"],"properties":{"detected":{"type":"boolean"},"anomalyScore":{"type":"number"},"antiDetectBrowser":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"PrivacySettingsSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"VirtualizationSummary":{"type":"object","required":["detected","confidence","indicators"],"properties":{"detected":{"type":"boolean"},"confidence":{"type":"string"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/VirtualizationIndicator"}}}},"VirtualizationIndicator":{"type":"object","required":["source","confidence"],"properties":{"source":{"type":"string"},"confidence":{"type":"string"}}},"IncognitoSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"ErrorResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Human-readable error message."},"stack":{"type":"string","nullable":true,"description":"Present only in non-production environments."}}}}},"paths":{"/request/event/{id}":{"get":{"tags":["Events"],"operationId":"getProcessedEventById","summary":"Get processed event by id","description":"Returns a processed view of an event for fraud / risk evaluation.\n\n**Authentication**\nPass your secret key as a Bearer token: `Authorization: Bearer <secret>`.\nSecrets must not be sent as query parameters — they would be rejected by the upstream WAF\nand exposed in access logs.\n\n**Event availability / eventual consistency**\nEvents are processed asynchronously; the event may temporarily return `404` shortly after creation.\nThe official Server SDK retries `404` for a short time window.\n","parameters":[{"in":"path","name":"id","required":true,"description":"Guardian event identifier (e.g. `evt_123`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Processed event payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessedEventResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized (missing or invalid secret key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment required (free quota exceeded)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden (event does not belong to the key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Event not found (may occur while the event is still processing)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## List processed events (paginated)

> Returns a paginated list of processed events for the authenticated site.\
> \
> \*\*Authentication\*\*\
> Pass your secret key as a Bearer token: \`Authorization: Bearer \<secret>\`.\
> Secrets must not be sent as query parameters — they would be rejected by the upstream WAF\
> and exposed in access logs.\
> \
> \*\*Pagination\*\*\
> Uses cursor-based pagination for stable, efficient traversal.\
> \- Use \`limit\` to control page size (1-100, default 20)\
> \- Use \`cursor\` from the previous response's \`pagination.nextCursor\` to fetch the next page\
> \- Check \`pagination.hasMore\` to determine if more results exist\
> \
> \*\*Filtering\*\*\
> \- \`visitorId\`: Filter events by visitor identifier\
> \- \`before\` / \`after\`: Filter by timestamp range (ISO 8601 format)\
> \- \`order\`: Sort direction (\`desc\` for newest first, \`asc\` for oldest first)<br>

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"tags":[{"name":"Events","description":"Retrieve processed events for server-side fraud/risk decisions."}],"servers":[{"url":"https://api.guardianstack.ai","description":"Production (default base URL used by the Server SDK)"},{"url":"http://localhost:3000","description":"Local development"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"token","description":"Use your Guardian **secret key** as a Bearer token.\n\nExample: `Authorization: Bearer sec_xxx`\n\nDo not pass the secret as a query parameter (`?secret=...`) — such requests are blocked\nby the upstream WAF and would leak credentials into access logs and referrers.\n"}},"schemas":{"PaginatedEventsResponse":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProcessedEventResponse"},"description":"Array of processed events for the current page."},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}},"ProcessedEventResponse":{"type":"object","required":["identification"],"properties":{"identification":{"$ref":"#/components/schemas/ProcessedEventIdentification"},"ipInfo":{"allOf":[{"$ref":"#/components/schemas/IpApiIsResponse"}],"nullable":true,"description":"IP intelligence payload used during processing.\nNote: some fields (e.g. `company.whois`, `asn.whois`) may be sanitized server-side.\n"},"vpn":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVpnSummary"}],"nullable":true},"velocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVelocity"}],"nullable":true,"description":"Event-level request velocity (counts) over sliding windows."},"visitorVelocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedVisitorVelocity"}],"nullable":true,"description":"Visitor-level request velocity (counts) over sliding windows."},"botDetection":{"allOf":[{"$ref":"#/components/schemas/BotDetectionSummary"}],"nullable":true},"tampering":{"allOf":[{"$ref":"#/components/schemas/TamperingSummary"}],"nullable":true},"privacySettings":{"allOf":[{"$ref":"#/components/schemas/PrivacySettingsSummary"}],"nullable":true},"virtualization":{"allOf":[{"$ref":"#/components/schemas/VirtualizationSummary"}],"nullable":true},"incognito":{"allOf":[{"$ref":"#/components/schemas/IncognitoSummary"}],"nullable":true}}},"ProcessedEventIdentification":{"type":"object","required":["id","ip"],"properties":{"id":{"type":"string","description":"Guardian event identifier."},"visitorId":{"type":"string","nullable":true,"description":"Stable, server-issued identifier for the visitor (site-scoped)."},"ip":{"type":"string","description":"Server-observed client IP address for the event."},"timestamp":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of event creation."},"url":{"type":"string","nullable":true,"description":"Page URL as reported by the client during collection."},"location":{"allOf":[{"$ref":"#/components/schemas/LocationInfo"}],"nullable":true},"browser":{"$ref":"#/components/schemas/BrowserInfo"}}},"LocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true,"description":"Continent code (e.g. `EU`, `AS`, `NA`)."},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"Local time at the IP-derived location (ISO string)."},"is_dst":{"type":"boolean","nullable":true}}},"BrowserInfo":{"type":"object","properties":{"browserName":{"type":"string","nullable":true},"browserMajorVersion":{"type":"string","nullable":true},"browserFullVersion":{"type":"string","nullable":true},"platform":{"type":"string","nullable":true,"description":"Normalized platform label (e.g. `windows`, `mac`, `ios`, `android`, `linux`, `chromeos`)."},"os":{"type":"string","nullable":true},"osVersion":{"type":"string","nullable":true},"device":{"type":"string","nullable":true},"userAgent":{"type":"string","nullable":true}}},"IpApiIsResponse":{"type":"object","required":["ip","is_bogon","is_mobile","is_satellite","is_crawler","is_datacenter","is_tor","is_proxy","is_vpn","is_abuser"],"properties":{"ip":{"type":"string"},"rir":{"type":"string","nullable":true,"description":"Regional Internet Registry (e.g. `RIPE`, `ARIN`)."},"is_bogon":{"type":"boolean"},"is_mobile":{"type":"boolean"},"is_satellite":{"type":"boolean"},"is_crawler":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_vpn":{"type":"boolean"},"is_abuser":{"type":"boolean"},"vpn":{"allOf":[{"$ref":"#/components/schemas/IpApiVpnInfo"}],"nullable":true},"datacenter":{"allOf":[{"$ref":"#/components/schemas/IpApiDatacenterInfo"}],"nullable":true},"company":{"allOf":[{"$ref":"#/components/schemas/IpApiCompanyInfo"}],"nullable":true},"abuse":{"allOf":[{"$ref":"#/components/schemas/IpApiAbuseContact"}],"nullable":true},"asn":{"allOf":[{"$ref":"#/components/schemas/IpApiAsnInfo"}],"nullable":true},"location":{"allOf":[{"$ref":"#/components/schemas/IpApiLocationInfo"}],"nullable":true},"elapsed_ms":{"type":"number","nullable":true,"description":"Upstream lookup latency in milliseconds (when available)."}}},"IpApiVpnInfo":{"type":"object","required":["ip","service","url","type","last_seen","last_seen_str"],"properties":{"ip":{"type":"string"},"service":{"type":"string"},"url":{"type":"string"},"type":{"type":"string","description":"Category (e.g. `vpn_server`)."},"last_seen":{"type":"number","description":"Epoch milliseconds."},"last_seen_str":{"type":"string","description":"ISO timestamp."},"exit_node_region":{"type":"string","nullable":true}}},"IpApiDatacenterInfo":{"type":"object","required":["datacenter","domain","network"],"properties":{"datacenter":{"type":"string"},"domain":{"type":"string"},"network":{"type":"string"}}},"IpApiCompanyInfo":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"abuser_score":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"description":"Organization type (e.g. `hosting`)."},"network":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiAbuseContact":{"type":"object","properties":{"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}},"IpApiAsnInfo":{"type":"object","required":["asn"],"properties":{"asn":{"type":"number"},"abuser_score":{"type":"string","nullable":true},"route":{"type":"string","nullable":true},"descr":{"type":"string","nullable":true},"country":{"type":"string","nullable":true,"description":"Lowercase country code."},"active":{"type":"boolean","nullable":true},"org":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"abuse":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"created":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"updated":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"rir":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiLocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"ISO string."},"local_time_unix":{"type":"number","nullable":true,"description":"Epoch seconds."},"is_dst":{"type":"boolean","nullable":true}}},"ProcessedEventVpnSummary":{"type":"object","required":["detected"],"properties":{"detected":{"type":"boolean","description":"Whether VPN/proxy usage is detected."},"confidence":{"type":"string","nullable":true,"description":"Confidence label (when available)."},"reason":{"type":"string","nullable":true,"description":"Reason/category for the VPN decision (when available)."},"browserTimezone":{"type":"string","nullable":true,"description":"Timezone reported by the browser/client signals."},"ipTimezone":{"type":"string","nullable":true,"description":"Timezone derived from IP intelligence."},"timezoneDifference":{"type":"number","nullable":true,"description":"Difference between browser and IP timezones in minutes (when available)."}}},"ProcessedEventVelocity":{"type":"object","required":["5m","1h","24h"],"properties":{"5m":{"type":"number","description":"Count in the last 5 minutes."},"1h":{"type":"number","description":"Count in the last 1 hour."},"24h":{"type":"number","description":"Count in the last 24 hours."}}},"ProcessedVisitorVelocity":{"type":"object","required":["5m","1h","24h","7d"],"properties":{"5m":{"type":"number"},"1h":{"type":"number"},"24h":{"type":"number"},"7d":{"type":"number"}}},"BotDetectionSummary":{"type":"object","required":["detected","score","automationSignalsPresent","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number","description":"Numeric score (higher generally indicates higher likelihood)."},"automationSignalsPresent":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}},"TamperingSummary":{"type":"object","required":["detected","anomalyScore","antiDetectBrowser","indicators"],"properties":{"detected":{"type":"boolean"},"anomalyScore":{"type":"number"},"antiDetectBrowser":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"PrivacySettingsSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"VirtualizationSummary":{"type":"object","required":["detected","confidence","indicators"],"properties":{"detected":{"type":"boolean"},"confidence":{"type":"string"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/VirtualizationIndicator"}}}},"VirtualizationIndicator":{"type":"object","required":["source","confidence"],"properties":{"source":{"type":"string"},"confidence":{"type":"string"}}},"IncognitoSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"PaginationMeta":{"type":"object","required":["limit","hasMore"],"properties":{"limit":{"type":"integer","description":"Maximum number of items per page."},"hasMore":{"type":"boolean","description":"Whether there are more items after this page."},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor to fetch the next page.\nPass this value as the `cursor` query parameter in the next request.\nAbsent when there are no more results.\n"},"totalCount":{"type":"integer","nullable":true,"description":"Total count of matching items (only included when explicitly requested)."}}},"ErrorResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Human-readable error message."},"stack":{"type":"string","nullable":true,"description":"Present only in non-production environments."}}}}},"paths":{"/request/events":{"get":{"tags":["Events"],"operationId":"getProcessedEvents","summary":"List processed events (paginated)","description":"Returns a paginated list of processed events for the authenticated site.\n\n**Authentication**\nPass your secret key as a Bearer token: `Authorization: Bearer <secret>`.\nSecrets must not be sent as query parameters — they would be rejected by the upstream WAF\nand exposed in access logs.\n\n**Pagination**\nUses cursor-based pagination for stable, efficient traversal.\n- Use `limit` to control page size (1-100, default 20)\n- Use `cursor` from the previous response's `pagination.nextCursor` to fetch the next page\n- Check `pagination.hasMore` to determine if more results exist\n\n**Filtering**\n- `visitorId`: Filter events by visitor identifier\n- `before` / `after`: Filter by timestamp range (ISO 8601 format)\n- `order`: Sort direction (`desc` for newest first, `asc` for oldest first)\n","parameters":[{"in":"query","name":"limit","required":false,"description":"Maximum number of events to return (1-100).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"in":"query","name":"cursor","required":false,"description":"Opaque cursor for pagination. Use `nextCursor` from the previous response.","schema":{"type":"string"}},{"in":"query","name":"visitorId","required":false,"description":"Filter events by visitor identifier.","schema":{"type":"string"}},{"in":"query","name":"before","required":false,"description":"Return events created before this timestamp (ISO 8601).","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"after","required":false,"description":"Return events created on or after this timestamp (ISO 8601).","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"order","required":false,"description":"Sort order by creation time.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Paginated list of processed events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedEventsResponse"}}}},"400":{"description":"Bad request (invalid cursor or parameters)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized (missing or invalid secret key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Models

## The ErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Human-readable error message."},"stack":{"type":"string","nullable":true,"description":"Present only in non-production environments."}}}}}}
```

## The PaginationMeta object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"PaginationMeta":{"type":"object","required":["limit","hasMore"],"properties":{"limit":{"type":"integer","description":"Maximum number of items per page."},"hasMore":{"type":"boolean","description":"Whether there are more items after this page."},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor to fetch the next page.\nPass this value as the `cursor` query parameter in the next request.\nAbsent when there are no more results.\n"},"totalCount":{"type":"integer","nullable":true,"description":"Total count of matching items (only included when explicitly requested)."}}}}}}
```

## The PaginatedEventsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"PaginatedEventsResponse":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProcessedEventResponse"},"description":"Array of processed events for the current page."},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}},"ProcessedEventResponse":{"type":"object","required":["identification"],"properties":{"identification":{"$ref":"#/components/schemas/ProcessedEventIdentification"},"ipInfo":{"allOf":[{"$ref":"#/components/schemas/IpApiIsResponse"}],"nullable":true,"description":"IP intelligence payload used during processing.\nNote: some fields (e.g. `company.whois`, `asn.whois`) may be sanitized server-side.\n"},"vpn":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVpnSummary"}],"nullable":true},"velocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVelocity"}],"nullable":true,"description":"Event-level request velocity (counts) over sliding windows."},"visitorVelocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedVisitorVelocity"}],"nullable":true,"description":"Visitor-level request velocity (counts) over sliding windows."},"botDetection":{"allOf":[{"$ref":"#/components/schemas/BotDetectionSummary"}],"nullable":true},"tampering":{"allOf":[{"$ref":"#/components/schemas/TamperingSummary"}],"nullable":true},"privacySettings":{"allOf":[{"$ref":"#/components/schemas/PrivacySettingsSummary"}],"nullable":true},"virtualization":{"allOf":[{"$ref":"#/components/schemas/VirtualizationSummary"}],"nullable":true},"incognito":{"allOf":[{"$ref":"#/components/schemas/IncognitoSummary"}],"nullable":true}}},"ProcessedEventIdentification":{"type":"object","required":["id","ip"],"properties":{"id":{"type":"string","description":"Guardian event identifier."},"visitorId":{"type":"string","nullable":true,"description":"Stable, server-issued identifier for the visitor (site-scoped)."},"ip":{"type":"string","description":"Server-observed client IP address for the event."},"timestamp":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of event creation."},"url":{"type":"string","nullable":true,"description":"Page URL as reported by the client during collection."},"location":{"allOf":[{"$ref":"#/components/schemas/LocationInfo"}],"nullable":true},"browser":{"$ref":"#/components/schemas/BrowserInfo"}}},"LocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true,"description":"Continent code (e.g. `EU`, `AS`, `NA`)."},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"Local time at the IP-derived location (ISO string)."},"is_dst":{"type":"boolean","nullable":true}}},"BrowserInfo":{"type":"object","properties":{"browserName":{"type":"string","nullable":true},"browserMajorVersion":{"type":"string","nullable":true},"browserFullVersion":{"type":"string","nullable":true},"platform":{"type":"string","nullable":true,"description":"Normalized platform label (e.g. `windows`, `mac`, `ios`, `android`, `linux`, `chromeos`)."},"os":{"type":"string","nullable":true},"osVersion":{"type":"string","nullable":true},"device":{"type":"string","nullable":true},"userAgent":{"type":"string","nullable":true}}},"IpApiIsResponse":{"type":"object","required":["ip","is_bogon","is_mobile","is_satellite","is_crawler","is_datacenter","is_tor","is_proxy","is_vpn","is_abuser"],"properties":{"ip":{"type":"string"},"rir":{"type":"string","nullable":true,"description":"Regional Internet Registry (e.g. `RIPE`, `ARIN`)."},"is_bogon":{"type":"boolean"},"is_mobile":{"type":"boolean"},"is_satellite":{"type":"boolean"},"is_crawler":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_vpn":{"type":"boolean"},"is_abuser":{"type":"boolean"},"vpn":{"allOf":[{"$ref":"#/components/schemas/IpApiVpnInfo"}],"nullable":true},"datacenter":{"allOf":[{"$ref":"#/components/schemas/IpApiDatacenterInfo"}],"nullable":true},"company":{"allOf":[{"$ref":"#/components/schemas/IpApiCompanyInfo"}],"nullable":true},"abuse":{"allOf":[{"$ref":"#/components/schemas/IpApiAbuseContact"}],"nullable":true},"asn":{"allOf":[{"$ref":"#/components/schemas/IpApiAsnInfo"}],"nullable":true},"location":{"allOf":[{"$ref":"#/components/schemas/IpApiLocationInfo"}],"nullable":true},"elapsed_ms":{"type":"number","nullable":true,"description":"Upstream lookup latency in milliseconds (when available)."}}},"IpApiVpnInfo":{"type":"object","required":["ip","service","url","type","last_seen","last_seen_str"],"properties":{"ip":{"type":"string"},"service":{"type":"string"},"url":{"type":"string"},"type":{"type":"string","description":"Category (e.g. `vpn_server`)."},"last_seen":{"type":"number","description":"Epoch milliseconds."},"last_seen_str":{"type":"string","description":"ISO timestamp."},"exit_node_region":{"type":"string","nullable":true}}},"IpApiDatacenterInfo":{"type":"object","required":["datacenter","domain","network"],"properties":{"datacenter":{"type":"string"},"domain":{"type":"string"},"network":{"type":"string"}}},"IpApiCompanyInfo":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"abuser_score":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"description":"Organization type (e.g. `hosting`)."},"network":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiAbuseContact":{"type":"object","properties":{"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}},"IpApiAsnInfo":{"type":"object","required":["asn"],"properties":{"asn":{"type":"number"},"abuser_score":{"type":"string","nullable":true},"route":{"type":"string","nullable":true},"descr":{"type":"string","nullable":true},"country":{"type":"string","nullable":true,"description":"Lowercase country code."},"active":{"type":"boolean","nullable":true},"org":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"abuse":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"created":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"updated":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"rir":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiLocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"ISO string."},"local_time_unix":{"type":"number","nullable":true,"description":"Epoch seconds."},"is_dst":{"type":"boolean","nullable":true}}},"ProcessedEventVpnSummary":{"type":"object","required":["detected"],"properties":{"detected":{"type":"boolean","description":"Whether VPN/proxy usage is detected."},"confidence":{"type":"string","nullable":true,"description":"Confidence label (when available)."},"reason":{"type":"string","nullable":true,"description":"Reason/category for the VPN decision (when available)."},"browserTimezone":{"type":"string","nullable":true,"description":"Timezone reported by the browser/client signals."},"ipTimezone":{"type":"string","nullable":true,"description":"Timezone derived from IP intelligence."},"timezoneDifference":{"type":"number","nullable":true,"description":"Difference between browser and IP timezones in minutes (when available)."}}},"ProcessedEventVelocity":{"type":"object","required":["5m","1h","24h"],"properties":{"5m":{"type":"number","description":"Count in the last 5 minutes."},"1h":{"type":"number","description":"Count in the last 1 hour."},"24h":{"type":"number","description":"Count in the last 24 hours."}}},"ProcessedVisitorVelocity":{"type":"object","required":["5m","1h","24h","7d"],"properties":{"5m":{"type":"number"},"1h":{"type":"number"},"24h":{"type":"number"},"7d":{"type":"number"}}},"BotDetectionSummary":{"type":"object","required":["detected","score","automationSignalsPresent","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number","description":"Numeric score (higher generally indicates higher likelihood)."},"automationSignalsPresent":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}},"TamperingSummary":{"type":"object","required":["detected","anomalyScore","antiDetectBrowser","indicators"],"properties":{"detected":{"type":"boolean"},"anomalyScore":{"type":"number"},"antiDetectBrowser":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"PrivacySettingsSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"VirtualizationSummary":{"type":"object","required":["detected","confidence","indicators"],"properties":{"detected":{"type":"boolean"},"confidence":{"type":"string"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/VirtualizationIndicator"}}}},"VirtualizationIndicator":{"type":"object","required":["source","confidence"],"properties":{"source":{"type":"string"},"confidence":{"type":"string"}}},"IncognitoSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"PaginationMeta":{"type":"object","required":["limit","hasMore"],"properties":{"limit":{"type":"integer","description":"Maximum number of items per page."},"hasMore":{"type":"boolean","description":"Whether there are more items after this page."},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor to fetch the next page.\nPass this value as the `cursor` query parameter in the next request.\nAbsent when there are no more results.\n"},"totalCount":{"type":"integer","nullable":true,"description":"Total count of matching items (only included when explicitly requested)."}}}}}}
```

## The ProcessedEventResponse object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"ProcessedEventResponse":{"type":"object","required":["identification"],"properties":{"identification":{"$ref":"#/components/schemas/ProcessedEventIdentification"},"ipInfo":{"allOf":[{"$ref":"#/components/schemas/IpApiIsResponse"}],"nullable":true,"description":"IP intelligence payload used during processing.\nNote: some fields (e.g. `company.whois`, `asn.whois`) may be sanitized server-side.\n"},"vpn":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVpnSummary"}],"nullable":true},"velocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedEventVelocity"}],"nullable":true,"description":"Event-level request velocity (counts) over sliding windows."},"visitorVelocity":{"allOf":[{"$ref":"#/components/schemas/ProcessedVisitorVelocity"}],"nullable":true,"description":"Visitor-level request velocity (counts) over sliding windows."},"botDetection":{"allOf":[{"$ref":"#/components/schemas/BotDetectionSummary"}],"nullable":true},"tampering":{"allOf":[{"$ref":"#/components/schemas/TamperingSummary"}],"nullable":true},"privacySettings":{"allOf":[{"$ref":"#/components/schemas/PrivacySettingsSummary"}],"nullable":true},"virtualization":{"allOf":[{"$ref":"#/components/schemas/VirtualizationSummary"}],"nullable":true},"incognito":{"allOf":[{"$ref":"#/components/schemas/IncognitoSummary"}],"nullable":true}}},"ProcessedEventIdentification":{"type":"object","required":["id","ip"],"properties":{"id":{"type":"string","description":"Guardian event identifier."},"visitorId":{"type":"string","nullable":true,"description":"Stable, server-issued identifier for the visitor (site-scoped)."},"ip":{"type":"string","description":"Server-observed client IP address for the event."},"timestamp":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of event creation."},"url":{"type":"string","nullable":true,"description":"Page URL as reported by the client during collection."},"location":{"allOf":[{"$ref":"#/components/schemas/LocationInfo"}],"nullable":true},"browser":{"$ref":"#/components/schemas/BrowserInfo"}}},"LocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true,"description":"Continent code (e.g. `EU`, `AS`, `NA`)."},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"Local time at the IP-derived location (ISO string)."},"is_dst":{"type":"boolean","nullable":true}}},"BrowserInfo":{"type":"object","properties":{"browserName":{"type":"string","nullable":true},"browserMajorVersion":{"type":"string","nullable":true},"browserFullVersion":{"type":"string","nullable":true},"platform":{"type":"string","nullable":true,"description":"Normalized platform label (e.g. `windows`, `mac`, `ios`, `android`, `linux`, `chromeos`)."},"os":{"type":"string","nullable":true},"osVersion":{"type":"string","nullable":true},"device":{"type":"string","nullable":true},"userAgent":{"type":"string","nullable":true}}},"IpApiIsResponse":{"type":"object","required":["ip","is_bogon","is_mobile","is_satellite","is_crawler","is_datacenter","is_tor","is_proxy","is_vpn","is_abuser"],"properties":{"ip":{"type":"string"},"rir":{"type":"string","nullable":true,"description":"Regional Internet Registry (e.g. `RIPE`, `ARIN`)."},"is_bogon":{"type":"boolean"},"is_mobile":{"type":"boolean"},"is_satellite":{"type":"boolean"},"is_crawler":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_vpn":{"type":"boolean"},"is_abuser":{"type":"boolean"},"vpn":{"allOf":[{"$ref":"#/components/schemas/IpApiVpnInfo"}],"nullable":true},"datacenter":{"allOf":[{"$ref":"#/components/schemas/IpApiDatacenterInfo"}],"nullable":true},"company":{"allOf":[{"$ref":"#/components/schemas/IpApiCompanyInfo"}],"nullable":true},"abuse":{"allOf":[{"$ref":"#/components/schemas/IpApiAbuseContact"}],"nullable":true},"asn":{"allOf":[{"$ref":"#/components/schemas/IpApiAsnInfo"}],"nullable":true},"location":{"allOf":[{"$ref":"#/components/schemas/IpApiLocationInfo"}],"nullable":true},"elapsed_ms":{"type":"number","nullable":true,"description":"Upstream lookup latency in milliseconds (when available)."}}},"IpApiVpnInfo":{"type":"object","required":["ip","service","url","type","last_seen","last_seen_str"],"properties":{"ip":{"type":"string"},"service":{"type":"string"},"url":{"type":"string"},"type":{"type":"string","description":"Category (e.g. `vpn_server`)."},"last_seen":{"type":"number","description":"Epoch milliseconds."},"last_seen_str":{"type":"string","description":"ISO timestamp."},"exit_node_region":{"type":"string","nullable":true}}},"IpApiDatacenterInfo":{"type":"object","required":["datacenter","domain","network"],"properties":{"datacenter":{"type":"string"},"domain":{"type":"string"},"network":{"type":"string"}}},"IpApiCompanyInfo":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"abuser_score":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"description":"Organization type (e.g. `hosting`)."},"network":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiAbuseContact":{"type":"object","properties":{"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}},"IpApiAsnInfo":{"type":"object","required":["asn"],"properties":{"asn":{"type":"number"},"abuser_score":{"type":"string","nullable":true},"route":{"type":"string","nullable":true},"descr":{"type":"string","nullable":true},"country":{"type":"string","nullable":true,"description":"Lowercase country code."},"active":{"type":"boolean","nullable":true},"org":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"abuse":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"created":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"updated":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"rir":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiLocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"ISO string."},"local_time_unix":{"type":"number","nullable":true,"description":"Epoch seconds."},"is_dst":{"type":"boolean","nullable":true}}},"ProcessedEventVpnSummary":{"type":"object","required":["detected"],"properties":{"detected":{"type":"boolean","description":"Whether VPN/proxy usage is detected."},"confidence":{"type":"string","nullable":true,"description":"Confidence label (when available)."},"reason":{"type":"string","nullable":true,"description":"Reason/category for the VPN decision (when available)."},"browserTimezone":{"type":"string","nullable":true,"description":"Timezone reported by the browser/client signals."},"ipTimezone":{"type":"string","nullable":true,"description":"Timezone derived from IP intelligence."},"timezoneDifference":{"type":"number","nullable":true,"description":"Difference between browser and IP timezones in minutes (when available)."}}},"ProcessedEventVelocity":{"type":"object","required":["5m","1h","24h"],"properties":{"5m":{"type":"number","description":"Count in the last 5 minutes."},"1h":{"type":"number","description":"Count in the last 1 hour."},"24h":{"type":"number","description":"Count in the last 24 hours."}}},"ProcessedVisitorVelocity":{"type":"object","required":["5m","1h","24h","7d"],"properties":{"5m":{"type":"number"},"1h":{"type":"number"},"24h":{"type":"number"},"7d":{"type":"number"}}},"BotDetectionSummary":{"type":"object","required":["detected","score","automationSignalsPresent","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number","description":"Numeric score (higher generally indicates higher likelihood)."},"automationSignalsPresent":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}},"TamperingSummary":{"type":"object","required":["detected","anomalyScore","antiDetectBrowser","indicators"],"properties":{"detected":{"type":"boolean"},"anomalyScore":{"type":"number"},"antiDetectBrowser":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"PrivacySettingsSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"VirtualizationSummary":{"type":"object","required":["detected","confidence","indicators"],"properties":{"detected":{"type":"boolean"},"confidence":{"type":"string"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/VirtualizationIndicator"}}}},"VirtualizationIndicator":{"type":"object","required":["source","confidence"],"properties":{"source":{"type":"string"},"confidence":{"type":"string"}}},"IncognitoSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}}}}}
```

## The ProcessedEventIdentification object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"ProcessedEventIdentification":{"type":"object","required":["id","ip"],"properties":{"id":{"type":"string","description":"Guardian event identifier."},"visitorId":{"type":"string","nullable":true,"description":"Stable, server-issued identifier for the visitor (site-scoped)."},"ip":{"type":"string","description":"Server-observed client IP address for the event."},"timestamp":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of event creation."},"url":{"type":"string","nullable":true,"description":"Page URL as reported by the client during collection."},"location":{"allOf":[{"$ref":"#/components/schemas/LocationInfo"}],"nullable":true},"browser":{"$ref":"#/components/schemas/BrowserInfo"}}},"LocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true,"description":"Continent code (e.g. `EU`, `AS`, `NA`)."},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"Local time at the IP-derived location (ISO string)."},"is_dst":{"type":"boolean","nullable":true}}},"BrowserInfo":{"type":"object","properties":{"browserName":{"type":"string","nullable":true},"browserMajorVersion":{"type":"string","nullable":true},"browserFullVersion":{"type":"string","nullable":true},"platform":{"type":"string","nullable":true,"description":"Normalized platform label (e.g. `windows`, `mac`, `ios`, `android`, `linux`, `chromeos`)."},"os":{"type":"string","nullable":true},"osVersion":{"type":"string","nullable":true},"device":{"type":"string","nullable":true},"userAgent":{"type":"string","nullable":true}}}}}}
```

## The LocationInfo object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"LocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true,"description":"Continent code (e.g. `EU`, `AS`, `NA`)."},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"Local time at the IP-derived location (ISO string)."},"is_dst":{"type":"boolean","nullable":true}}}}}}
```

## The BrowserInfo object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"BrowserInfo":{"type":"object","properties":{"browserName":{"type":"string","nullable":true},"browserMajorVersion":{"type":"string","nullable":true},"browserFullVersion":{"type":"string","nullable":true},"platform":{"type":"string","nullable":true,"description":"Normalized platform label (e.g. `windows`, `mac`, `ios`, `android`, `linux`, `chromeos`)."},"os":{"type":"string","nullable":true},"osVersion":{"type":"string","nullable":true},"device":{"type":"string","nullable":true},"userAgent":{"type":"string","nullable":true}}}}}}
```

## The ProcessedEventVpnSummary object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"ProcessedEventVpnSummary":{"type":"object","required":["detected"],"properties":{"detected":{"type":"boolean","description":"Whether VPN/proxy usage is detected."},"confidence":{"type":"string","nullable":true,"description":"Confidence label (when available)."},"reason":{"type":"string","nullable":true,"description":"Reason/category for the VPN decision (when available)."},"browserTimezone":{"type":"string","nullable":true,"description":"Timezone reported by the browser/client signals."},"ipTimezone":{"type":"string","nullable":true,"description":"Timezone derived from IP intelligence."},"timezoneDifference":{"type":"number","nullable":true,"description":"Difference between browser and IP timezones in minutes (when available)."}}}}}}
```

## The ProcessedEventVelocity object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"ProcessedEventVelocity":{"type":"object","required":["5m","1h","24h"],"properties":{"5m":{"type":"number","description":"Count in the last 5 minutes."},"1h":{"type":"number","description":"Count in the last 1 hour."},"24h":{"type":"number","description":"Count in the last 24 hours."}}}}}}
```

## The ProcessedVisitorVelocity object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"ProcessedVisitorVelocity":{"type":"object","required":["5m","1h","24h","7d"],"properties":{"5m":{"type":"number"},"1h":{"type":"number"},"24h":{"type":"number"},"7d":{"type":"number"}}}}}}
```

## The IndicatorSeverity object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}}}}}
```

## The BotDetectionSummary object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"BotDetectionSummary":{"type":"object","required":["detected","score","automationSignalsPresent","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number","description":"Numeric score (higher generally indicates higher likelihood)."},"automationSignalsPresent":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}}}}}
```

## The TamperingSummary object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"TamperingSummary":{"type":"object","required":["detected","anomalyScore","antiDetectBrowser","indicators"],"properties":{"detected":{"type":"boolean"},"anomalyScore":{"type":"number"},"antiDetectBrowser":{"type":"boolean"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}}}}}
```

## The PrivacySettingsSummary object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"PrivacySettingsSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}}}}}
```

## The VirtualizationIndicator object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"VirtualizationIndicator":{"type":"object","required":["source","confidence"],"properties":{"source":{"type":"string"},"confidence":{"type":"string"}}}}}}
```

## The VirtualizationSummary object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"VirtualizationSummary":{"type":"object","required":["detected","confidence","indicators"],"properties":{"detected":{"type":"boolean"},"confidence":{"type":"string"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/VirtualizationIndicator"}}}},"VirtualizationIndicator":{"type":"object","required":["source","confidence"],"properties":{"source":{"type":"string"},"confidence":{"type":"string"}}}}}}
```

## The IncognitoSummary object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IncognitoSummary":{"type":"object","required":["detected","score","indicators"],"properties":{"detected":{"type":"boolean"},"score":{"type":"number"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorSeverity"}}}},"IndicatorSeverity":{"type":"object","required":["source","severity"],"properties":{"source":{"type":"string"},"severity":{"type":"string","description":"Severity label for this indicator."}}}}}}
```

## The IpApiIsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IpApiIsResponse":{"type":"object","required":["ip","is_bogon","is_mobile","is_satellite","is_crawler","is_datacenter","is_tor","is_proxy","is_vpn","is_abuser"],"properties":{"ip":{"type":"string"},"rir":{"type":"string","nullable":true,"description":"Regional Internet Registry (e.g. `RIPE`, `ARIN`)."},"is_bogon":{"type":"boolean"},"is_mobile":{"type":"boolean"},"is_satellite":{"type":"boolean"},"is_crawler":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_vpn":{"type":"boolean"},"is_abuser":{"type":"boolean"},"vpn":{"allOf":[{"$ref":"#/components/schemas/IpApiVpnInfo"}],"nullable":true},"datacenter":{"allOf":[{"$ref":"#/components/schemas/IpApiDatacenterInfo"}],"nullable":true},"company":{"allOf":[{"$ref":"#/components/schemas/IpApiCompanyInfo"}],"nullable":true},"abuse":{"allOf":[{"$ref":"#/components/schemas/IpApiAbuseContact"}],"nullable":true},"asn":{"allOf":[{"$ref":"#/components/schemas/IpApiAsnInfo"}],"nullable":true},"location":{"allOf":[{"$ref":"#/components/schemas/IpApiLocationInfo"}],"nullable":true},"elapsed_ms":{"type":"number","nullable":true,"description":"Upstream lookup latency in milliseconds (when available)."}}},"IpApiVpnInfo":{"type":"object","required":["ip","service","url","type","last_seen","last_seen_str"],"properties":{"ip":{"type":"string"},"service":{"type":"string"},"url":{"type":"string"},"type":{"type":"string","description":"Category (e.g. `vpn_server`)."},"last_seen":{"type":"number","description":"Epoch milliseconds."},"last_seen_str":{"type":"string","description":"ISO timestamp."},"exit_node_region":{"type":"string","nullable":true}}},"IpApiDatacenterInfo":{"type":"object","required":["datacenter","domain","network"],"properties":{"datacenter":{"type":"string"},"domain":{"type":"string"},"network":{"type":"string"}}},"IpApiCompanyInfo":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"abuser_score":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"description":"Organization type (e.g. `hosting`)."},"network":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiAbuseContact":{"type":"object","properties":{"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}},"IpApiAsnInfo":{"type":"object","required":["asn"],"properties":{"asn":{"type":"number"},"abuser_score":{"type":"string","nullable":true},"route":{"type":"string","nullable":true},"descr":{"type":"string","nullable":true},"country":{"type":"string","nullable":true,"description":"Lowercase country code."},"active":{"type":"boolean","nullable":true},"org":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"abuse":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"created":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"updated":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"rir":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}},"IpApiLocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"ISO string."},"local_time_unix":{"type":"number","nullable":true,"description":"Epoch seconds."},"is_dst":{"type":"boolean","nullable":true}}}}}}
```

## The IpApiVpnInfo object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IpApiVpnInfo":{"type":"object","required":["ip","service","url","type","last_seen","last_seen_str"],"properties":{"ip":{"type":"string"},"service":{"type":"string"},"url":{"type":"string"},"type":{"type":"string","description":"Category (e.g. `vpn_server`)."},"last_seen":{"type":"number","description":"Epoch milliseconds."},"last_seen_str":{"type":"string","description":"ISO timestamp."},"exit_node_region":{"type":"string","nullable":true}}}}}}
```

## The IpApiDatacenterInfo object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IpApiDatacenterInfo":{"type":"object","required":["datacenter","domain","network"],"properties":{"datacenter":{"type":"string"},"domain":{"type":"string"},"network":{"type":"string"}}}}}}
```

## The IpApiCompanyInfo object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IpApiCompanyInfo":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"abuser_score":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"description":"Organization type (e.g. `hosting`)."},"network":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}}}}}
```

## The IpApiAbuseContact object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IpApiAbuseContact":{"type":"object","properties":{"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}}}}}
```

## The IpApiAsnInfo object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IpApiAsnInfo":{"type":"object","required":["asn"],"properties":{"asn":{"type":"number"},"abuser_score":{"type":"string","nullable":true},"route":{"type":"string","nullable":true},"descr":{"type":"string","nullable":true},"country":{"type":"string","nullable":true,"description":"Lowercase country code."},"active":{"type":"boolean","nullable":true},"org":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"abuse":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"created":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"updated":{"type":"string","nullable":true,"description":"YYYY-MM-DD."},"rir":{"type":"string","nullable":true},"whois":{"type":"string","nullable":true,"description":"May be omitted or sanitized server-side."}}}}}}
```

## The IpApiLocationInfo object

```json
{"openapi":"3.0.3","info":{"title":"Guardian Stack API","version":"1.0.0"},"components":{"schemas":{"IpApiLocationInfo":{"type":"object","properties":{"is_eu_member":{"type":"boolean","nullable":true},"calling_code":{"type":"string","nullable":true},"currency_code":{"type":"string","nullable":true},"continent":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"latitude":{"type":"number","format":"float","nullable":true},"longitude":{"type":"number","format":"float","nullable":true},"zip":{"type":"string","nullable":true},"timezone":{"type":"string","nullable":true},"local_time":{"type":"string","nullable":true,"description":"ISO string."},"local_time_unix":{"type":"number","nullable":true,"description":"Epoch seconds."},"is_dst":{"type":"boolean","nullable":true}}}}}}
```


# Changelog

New updates and improvements

## December 2025 - Visitor Identity (Server-Issued Visitor ID)

We introduced a **server-issued, site-scoped `visitorId`** to reliably link events from the same real device across sessions, without depending on client-side storage.

{% columns %}
{% column %}

<figure><img src="https://gitbookio.github.io/onboarding-template-images/placeholder.png" alt=""><figcaption></figcaption></figure>
{% endcolumn %}

{% column %}

#### Reliable Device Linking

* Introduced advanced server-side visitor identification to track devices across sessions without persistent client-side storage
* Implemented consistent ID generation logic to ensure reliable user recognition across visits
* Added intelligent matching capabilities to handle minor environment fluctuations and updates

#### Enhanced Matching Accuracy

* Incorporated robust multi-signal device analysis to achieve higher identification precision
* Added network-level intelligence and tie-breaking logic to significantly reduce false positives
* Improved identification stability across changing network conditions and browsing contexts

#### Privacy Mode Resilience

* Improved identification performance in incognito and private browsing modes
* Enhanced stability for privacy-hardened browser configurations and strict privacy settings
* Maintained accuracy even when common anti-fingerprinting measures are active

#### Strict Signal Validation

* Implemented advanced validation logic to prevent false positive matches between similar devices
* Added strict consistency checks for high-confidence signals to ensure identity integrity
* Enforced rigorous verification standards to distinguish between legitimate users and emulation attempts
  {% endcolumn %}
  {% endcolumns %}

## October 2025 - 🚧 Closed Beta - Internal Testing Phase

Guardian Stack is currently in closed beta with select partners. This changelog tracks our progress as we refine fraud detection accuracy and expand platform capabilities.

{% columns %}
{% column %}

<figure><img src="https://gitbookio.github.io/onboarding-template-images/placeholder.png" alt=""><figcaption></figcaption></figure>
{% endcolumn %}

{% column %}

#### Enhanced Bot Detection Algorithm

* Improved detection accuracy for sophisticated headless browsers and automation frameworks
* Added support for identifying newer anti-detect browser signatures
* Reduced false positives on legitimate browser extensions and accessibility tools

[Read the documentation](/documentation)

#### VPN Detection Improvements

* Enhanced residential proxy detection across major providers
* Better accuracy for legitimate privacy tools vs. fraud-focused VPN usage
* Added confidence scoring for VPN detection results

[Read the documentation](/documentation)

#### Server SDK Helper Functions

* Introduced boolean helper functions for quick fraud decisions (isBot(), isVPN(), isTampering())
* Added detailed indicator extraction methods for advanced analysis
* Improved error handling and null-safe operations across all helpers

[Read the documentation](/documentation)
{% endcolumn %}
{% endcolumns %}

***


# Help Center

<h2 align="center">What can we help you find?</h2>

<p align="center">Browse the topics below or use the GitBook assistant to ask anything you need help with.</p>

<p align="center"> <a href="mailto:support@guardianstack.ai" class="button primary">Contact support</a></p>

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><i class="fa-leaf">:leaf:</i></h4></td><td><strong>Getting started</strong></td><td>Get help with the basics</td><td><a href="/documentation">Welcome to Guardian</a></td></tr></tbody></table>


