`, and a few lines of JavaScript. Here’s the final version of the login page markup:
```
extends layout
block content
h1 Log In
form(method="post" action="/login")
label(for="username") Username
input(type="text" id="username" name="username" required)
label(for="password") Password
input(type="password" id="password" name="password" required)
- div.frc-risk-intelligence(data-sitekey=process.env.FRC_SITEKEY)
+ div.frc-risk-intelligence(data-sitekey=process.env.FRC_SITEKEY data-start="auto")
- button(type="submit") Log In
+ button(type="submit" disabled) Log In
script(type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@1.0.2/site.min.js" async defer)
script(nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@1.0.2/site.compat.min.js" async defer)
+ script.
+ document.addEventListener("DOMContentLoaded", () => {
+ const el = document.querySelector(".frc-risk-intelligence");
+ const btn = document.querySelector('button[type="submit"]');
+ el.addEventListener("frc:riskintelligence.complete", () => {
+ btn.disabled = false;
+ });
+ el.addEventListener("frc:riskintelligence.error", (e) => {
+ console.warn("Risk Intelligence token generation errored", e.detail);
+ btn.disabled = false;
+ });
+ });
```
If you refresh the page, the token will be generated automatically before the form is focused.
## Retrieving the Risk Intelligence data[](#retrieving-the-risk-intelligence-data "Direct link to Retrieving the Risk Intelligence data")
When the form is submitted, our server-side handler receives a POST request with the username and a Risk Intelligence token. We need to pass that token to the Friendly Captcha API to retrieve the Risk Intelligence data, which will include the browser information.
To communicate with the Friendly Captcha API, we’ll use the `@friendlycaptcha/server-sdk` library, which we can install from NPM:
```
npm install @friendlycaptcha/server-sdk
```
With that installed, we can instantiate a `FriendlyCaptchaClient` that we’ll use to retrieve Risk Intelligence data using the token:
```
import express from "express";
import session from "express-session";
+import { FriendlyCaptchaClient } from "@friendlycaptcha/server-sdk";
import * as store from "./store.js";
const app = express();
const port = process.env.PORT || 3000;
+const frcClient = new FriendlyCaptchaClient({
+ apiKey: process.env.FRC_APIKEY,
+ sitekey: process.env.FRC_SITEKEY,
+});
+
```
Next we can update the `POST /login` handler to extract the Risk Intelligence token from the form:
```
app.post(
"/login",
express.urlencoded({ extended: false }),
async (req, res) => {
req.session.user = authenticate(req.body.username);
+ const browser = await getBrowser(req.body["frc-risk-intelligence-token"]);
+ console.log(`User ${req.session.user.name} logged in from ${browser || "an unknown browser"}`);
req.session.save(() => res.redirect("/"));
},
);
```
We’re now calling a `getBrowser` function to discover the browser used and then logging a message. `getBrowser` encapsulates the Friendly Captcha API call, and the implementation is copied from [the project README](https://github.com/FriendlyCaptcha/friendly-captcha-javascript/?tab=readme-ov-file#retrieving-risk-intelligence) with some minor modifications. Here it is in its entirety:
```
async function getBrowser(token) {
if (!token) {
return console.warn(
"Empty token, skipping Risk Intelligence data retrieval.",
);
}
const result = await frcClient.retrieveRiskIntelligence(token);
// Check if we were able to retrieve the risk intelligence data
if (result.wasAbleToRetrieve()) {
// Check if the token is valid and data was retrieved successfully
if (result.isValid()) {
const response = result.getResponse();
const { browser } = response.data.risk_intelligence.client;
return `${browser.name}, version ${browser.version}`;
} else {
// Token was invalid or expired
const error = result.getResponseError();
console.log("Error:", error?.error_code, error?.detail);
}
} else {
// Network issue or configuration problem
if (result.isClientError()) {
console.log("Configuration error - check your API key");
} else {
console.log("Network issue or service temporarily unavailable");
}
}
}
```
Try restarting the server and logging in from a multiple browsers. Here are my server logs:
```
Server listening on port 3000
User friendly logged in from Firefox, version 148
User friendly logged in from Safari, version 17.6
User friendly logged in from Chrome, version 145
```
**Without a back-end SDK**
The implementation above uses the `@friendlycaptcha/server-sdk` library, but you can also send a plain HTTP request. That might look something like this:
```
async function getBrowser(token) {
try {
const response = await fetch("https://global.frcapi.com/api/v2/riskIntelligence/retrieve", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.FRC_APIKEY,
},
body: JSON.stringify({ token }),
});
const body = await response.text();
const parsed = JSON.parse(body);
if (parsed.error) {
throw parsed.error;
} else if (!response.ok) {
throw body;
}
const { name, version } = parsed.data.risk_intelligence.client.browser;
return `${name}, version ${version}`;
} catch (error) {
console.warn("Failed to retrieve Risk Intelligence data", error);
}
}
```
## Risk-based authentication[](#risk-based-authentication "Direct link to Risk-based authentication")
Now that we have the browser information, we can keep track of which browsers we’ve seen for a given user, and perform some special logic if they attempt to log in from a browser we haven’t seen yet. There are a couple of changes we need to make. Let’s start by updating the user model.
```
@@ -7,6 +7,7 @@ export function authenticate(username) {
user = {
name: username,
loginCount: 0,
+ browsers: new Set(),
};
users.set(username, user);
}
@@ -17,7 +18,18 @@ export function getUser(username) {
return users.get(username);
}
-export function recordLogin(username) {
+export function recordLogin(username, browser) {
const user = getUser(username);
user.loginCount++;
+ user.browsers.add(browser);
+}
+
+export function shouldConfirm(username, browser) {
+ const user = getUser(username);
+
+ // There needs to be at least 1 recognized browser
+ // for there to be an unrecognized browser.
+ if (user.browsers.size < 1) return false;
+
+ return !user.browsers.has(browser);
}
```
Users now have a `Set` to keep track of the browsers they’ve used; when we record a visit from a user, we update the set, and there’s a new function to check if the user needs to confirm the login. Since our risk-based authentication is based on the user logging in from an unrecognized browser, `shouldConfirm` returns true if the current browser is not in the list of previously used browsers.
We also need to update the logic in our app’s routes to support the confirmation flow. Let’s start with the `POST /login` handler.
```
app.post(
express.urlencoded({ extended: false }),
async (req, res) => {
const user = store.authenticate(req.body.username);
- store.recordLogin(user.name);
const browser = await getBrowser(req.body["frc-risk-intelligence-token"]);
- console.log(
- User ${user.name} logged in from ${browser || "an unknown browser"},
- );
+
+ let nextRoute;
+ if (store.shouldConfirm(user.name, browser)) {
+ // Here you might generate a confirmation code and email it to the user's account,
+ // but we're going to skip it as part of this tutorial for the sake of simplicity.
+ nextRoute = "/confirm";
+ // Store the browser in the session so we can use it in the POST /confirm route.
+ req.session.browser = browser;
+ } else {
+ nextRoute = "/";
+ store.recordLogin(user.name, browser);
+ }
+
req.session.username = user.name;
- req.session.save(() => res.redirect("/"));
+ req.session.save(() => res.redirect(nextRoute));
},
);
```
We’re going to use the new `store.shouldConfirm()` function to branch on whether the user’s browser is recognized or not.
If it’s not recognized, we’re redirecting to a new `/confirm` page and storing the browser in the session object (we’ll need it in another handler soon). As mentioned in the comment, a more realistic implementation would generate a confirmation code and email it to the user.
If the browser *is* recognized, we record the new login and redirect to the index page.
There’s a new `GET /confirm` route that renders the confirmation page. Here’s the tiny handler:
```
app.get("/confirm", (req, res) => {
if (!req.session.username) return res.redirect("/login");
res.render("confirm", {
title: "Confirm Login",
username: req.session.username,
});
});
```
And the template, saved to `views/confirm.pug`:
```
extends layout
block content
h1 Is that you, #{username}?
p(style="width: 60ch") We noticed that you're logging in from a browser we haven't seen before. For your security, we've sent an email with a confirmation code to the address we have on file. Please enter the confirmation code below to complete your login.
form(method="post" action="/confirm")
label(for="confirmation") Confirmation Code
input(type="text" id="confirmation" name="confirmation" required)
button(type="submit") Confirm
```
This is what the page looks like in the browser:

Like the login page, this form accepts any input. Here’s the `POST /confirm` handler that process this form submission:
```
app.post("/confirm", express.urlencoded({ extended: false }), (req, res) => {
// The confirmation code is available in req.body.confirmation.
// You would compare it to the one you generated in the POST /login handler.
store.recordLogin(req.session.username, req.session.browser);
res.redirect("/");
});
```
Upon successful confirmation, we record the login and redirect to the index page. We use the browser that we stored in the session in the `POST /login` handler.
With a risk-based authentication flow like this one, your login flow will be more robust against account takeover. There will be no additional friction for users authenticating from their usual browsers. In the event of a credential leak, an attacker would need to match the compromised user’s browser.
You can extend this check to [any of the other fields offered by Risk Intelligence](/docs/v2/risk-intelligence/format.md). For example, you might require a confirmation if a user is logging in from a new country or network. To learn more, check out the [**Risk Intelligence**](/docs/v2/risk-intelligence/.md) docs or read through the [**Getting Started**](/docs/v2/risk-intelligence/getting-started/.md) guide.
---
# Versioning and Immutability
How we version and publish the Friendly Captcha browser SDK, and how you can pin and verify the exact code that runs on your website.
This applies to both our v2 package [`@friendlycaptcha/sdk`](https://www.npmjs.com/package/@friendlycaptcha/sdk) and our v1 package [`friendly-challenge`](https://www.npmjs.com/package/friendly-challenge). For how long v1 will be supported, see [**v1 and v2**](/docs/v2/versions.md#whats-going-to-happen-to-v1).
## Versioning[](#versioning "Direct link to Versioning")
Our browser SDK follows [Semantic Versioning](https://semver.org/). In practice we have never shipped a release that wasn't backwards compatible.
## Immutable Releases[](#immutable-releases "Direct link to Immutable Releases")
**We never re-release a version.** Once published, the files served under a version number never change. This is also enforced by the npm registry itself, which permanently reserves a version number once it has been used.
We have never unpublished a version, and we have no plans to. Old pins should keep working indefinitely.
## CDN[](#cdn "Direct link to CDN")
### Version Pinning[](#version-pinning "Direct link to Version Pinning")
Always reference an exact version in your script tags:
```
```
tip
Pinning means you decide when to upgrade, but also that you don't automatically receive fixes. We recommend that you update your pinned version periodically, with reference to [the changelog](https://github.com/FriendlyCaptcha/friendly-captcha-sdk/blob/main/CHANGELOG.md).
### Subresource Integrity[](#subresource-integrity "Direct link to Subresource Integrity")
[Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) (SRI) pins the file's *contents* with an integrity hash, rather than just its URL. Because the CDN is operated by a third party, this is how you verify on every page load that the code reaching your users is the code you reviewed.
info
Our site scripts don't load any further JavaScript at runtime, so the two integrity hashes cover all of the JavaScript that the SDK executes on your page.
```
```
You can run this from the command-line to calculate the integrity hash for a specific file and version.
```
curl -sSLf https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@1.0.2/site.min.js \
| openssl dgst -sha384 -binary | openssl base64 -A
```
Alternatively, there are some websites that will do this for you, like
.
## Self-hosting[](#self-hosting "Direct link to Self-hosting")
Using `cdn.jsdelivr.net` is optional. You can [download the release files](/docs/v2/getting-started/install.md#using-the-scripts-without-a-cdn-ie-self-hosting) and serve them from your own infrastructure, removing the third-party CDN from your supply chain entirely.
---
# Self-Hosted Endpoint
info
The Self-Hosted Endpoint feature is only available for Friendly Captcha v2.
When a customer website or application loads a Friendly Captcha widget, the widget makes a number of requests to the Friendly Captcha API. The API endpoint is `global.frcapi.com`, or `eu.frcapi.com` for customers who use [the EU Endpoint](/docs/v2/guides/eu-endpoint.md). Friendly Captcha offers the **Self-Hosted Endpoint** feature for customers who prefer to route all end-user traffic through their own infrastructure.
To use a Self-Hosted Endpoint, customers operate a proxy server that forwards widget requests to the Friendly Captcha API. Because the Friendly Captcha service depends on data sent by the widget, it is important to ensure that a Self-Hosted Endpoint correctly forwards that data.
## Setup[](#setup "Direct link to Setup")
There are 3 steps to setting up a Self-Hosted Endpoint, explained in detail below.
1. [Generate a proxy key in the Friendly Captcha dashboard.](#1-generate-a-proxy-key)
2. [Configure your web server to proxy widget requests to the Friendly Captcha API.](#2-configure-your-web-server)
3. [Configure your widget to use your Self-Hosted Endpoint.](#3-configure-your-site-or-apps-widget)
### 1. Generate a proxy key[](#1-generate-a-proxy-key "Direct link to 1. Generate a proxy key")
To verify that proxied widget requests come from your infrastructure, you must set a header that contains a proxy key. You can generate a key in the [Friendly Captcha dashboard](https://app.friendlycaptcha.eu/dashboard/accounts/-/keys/proxy). Make sure to generate a **Proxy Key**; API keys are not accepted. Store the generated key somewhere safe and retrievable—Friendly Captcha doesn't keep a copy of the key, so you will have to regenerate it if you lose it. You will use this proxy key in the next step.
### 2. Configure your web server[](#2-configure-your-web-server "Direct link to 2. Configure your web server")
You need to configure your web server to forward the following requests to the Friendly Captcha API:
```
GET /api/v2/captcha/agent
GET /api/v2/captcha/widget
GET /api/v2/captcha/ping
POST /api/v2/captcha/activate
POST /api/v2/captcha/quote
POST /api/v2/captcha/redeem
```
For these requests, your server should forward the request in its entirety, including all headers. Additionally, the forwarded requests should include two ***extra*** headers:
1. `X-Frc-Proxy-Key`: The proxy key you generated in the Friendly Captcha dashboard.
2. `X-Frc-Proxy-Client-IP`: The original (source) IP address of the end user.
Forward the requests to this endpoint (i.e., the upstream server):
```
https://global.proxy.frcapi.com
```
If you have access to [the EU Endpoint](/docs/v2/guides/eu-endpoint.md), you may alternatively forward the requests to this endpoint:
```
https://eu.proxy.frcapi.com
```
[See below](#example-routing-configurations) for examples of how to configure a server to correctly proxy the widget requests.
Note
If you don't forward the entire request and its headers, the widget may still be functional, but the service will operate in a degraded state. If you forget either of the additional proxy headers (`X-Frc-Proxy-Key` and `X-Frc-Proxy-Client-IP`), the widget will display an error message.
### 3. Configure your site or app's widget[](#3-configure-your-site-or-apps-widget "Direct link to 3. Configure your site or app's widget")
The final step is to configure your server's URL as the API endpoint for your widget. This will ensure that the widget sends its requests to your server (which will then forward them to the upstream Friendly Captcha API). If your server's URL is `https://example.com` and you configure the widget using HTML `data-` attributes, your markup will look something like this:
```
```
If you configure the widget using the JavaScript SDK, your code will look something like this:
```
import { FriendlyCaptchaSDK } from "@friendlycaptcha/sdk"
const sdk = new FriendlyCaptchaSDK();
const mount = document.querySelector(".my-widget");
const widget = sdk.createWidget({
element: mount,
sitekey: "
",
apiEndpoint: "https://example.com",
});
```
For more documentation, see the page on [configuring the Friendly Captcha widget](/docs/v2/sdk/configuration.md).
## Example Routing Configurations[](#example-routing-configurations "Direct link to Example Routing Configurations")
Provided below are some example Self-Hosted Endpoints configurations for popular web server technologies. If you copy and paste the configuration, make sure to replace `<% PROXY KEY %>` with your real proxy key, and verify that you're using the correct upstream Friendly Captcha API endpoint.
### Apache[](#apache "Direct link to Apache")
```
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule headers_module modules/mod_headers.so
RequestHeader set X-Frc-Proxy-Key "<% PROXY KEY %>"
RequestHeader set X-Frc-Proxy-Client-IP expr=%{REMOTE_ADDR}
ProxyPass https://global.proxy.frcapi.com
ProxyPassReverse https://global.proxy.frcapi.com
```
### Caddy[](#caddy "Direct link to Caddy")
```
@captcha_paths path_regexp ^/api/v2/captcha/(agent|widget|ping|activate|quote|redeem)(/.*)?$
reverse_proxy @captcha_paths https://global.proxy.frcapi.com {
header_up X-Frc-Proxy-Key "<% PROXY KEY %>"
header_up X-Frc-Proxy-Client-IP {remote_host}
}
```
### HAProxy[](#haproxy "Direct link to HAProxy")
```
frontend http-in
# other frontend configuration...
acl is_captcha_path path_reg ^/api/v2/captcha/(agent|widget|ping|activate|quote|redeem)(/.*)?$
use_backend captcha_paths if is_captcha_path
backend captcha_paths
mode http
http-request set-header X-Frc-Proxy-Key "<% PROXY KEY %>"
http-request set-header X-Frc-Proxy-Client-IP %[src]
# Note: the path to the certificates file may be different for your OS.
server frc_api global.proxy.frcapi.com:443 ssl verify required ca-file /etc/ssl/certs/ca-certificates.crt
```
### NGINX[](#nginx "Direct link to NGINX")
```
location ~ ^/api/v2/captcha/(agent|widget|ping|activate|quote|redeem)(/.*)?$ {
proxy_set_header X-Frc-Proxy-Key <% PROXY KEY %>;
proxy_set_header X-Frc-Proxy-Client-IP $remote_addr;
proxy_pass https://global.proxy.frcapi.com;
}
```
## Troubleshooting[](#troubleshooting "Direct link to Troubleshooting")
### Restrictive `X-Frame-Options`[](#restrictive-x-frame-options "Direct link to restrictive-x-frame-options")
The Friendly Captcha widget loads in an `iframe`, which means it will not load if your proxied response includes an `X-Frame-Options: DENY` header. If the widget will not load via your Self-Hosted Endpoint, check that any `X-Frame-Options` headers on the proxied responses are permissive enough to allow the `iframe` to load. You can choose one of the following three options.
1. Don't set the `X-Frame-Options` header at all (this is what the Friendly Captcha API does).
2. Set `X-Frame-Options: SAMEORIGIN` if the page and proxied requests have the same origin.
3. Use the `frame-ancestors` directive of the `Content-Security-Policy` header to specifically allow `iframe`s loaded from your Self-Hosted Endpoint origin.
---
# Upgrading from v1 to v2
This guide describes how to upgrade from v1 to v2 of Friendly Captcha.
To learn more about the ways v2 improves upon v1, and why upgrading is worth the effort, [**click here**](/docs/v2/versions.md#whats-new-in-v2).
## Changes to your website (front-end changes)[](#changes-to-your-website-front-end-changes "Direct link to Changes to your website (front-end changes)")
### Script tag installation[](#script-tag-installation "Direct link to Script tag installation")
If you installed Friendly Captcha to your website by adding a `
```
with the new `@friendlycaptcha/sdk` scripts
```
```
## 2. 🇪🇺 Update custom API endpoints[](#2--update-custom-api-endpoints "Direct link to 2. 🇪🇺 Update custom API endpoints")
If you are using a specific endpoint, you need to update the `data-puzzle-endpoint` attribute. The attribute is now called `data-api-endpoint` and it supports shortcuts (`"eu"` for our dedicated EU endpoint).
For example, if you are using the dedicated EU-only endpoint, you would replace
```
```
with
```
```
## 3. Remove `data-lang`[](#3-remove-data-lang "Direct link to 3-remove-data-lang")
Remove the `data-lang="..."` attributes from your widgets. The new v2 widget automatically matches the language on your website.
**Example**
```
```
becomes
```
```
If you still want to explicitly force a specific language, replace `data-lang` with `lang`.
## 4. Update your callbacks into event listeners[](#4-update-your-callbacks-into-event-listeners "Direct link to 4. Update your callbacks into event listeners")
If specify `data-callback`, `data-error-callback` or `data-expired-callback` on your widget, these need to be replaced with event handlers.
If you are currently using these callbacks to enable and disable a submit button, your code may look like this:
```
```
```
```
You would replace it with the following
```
```
```
```
For more information around the events and possible states, see [the Events documentation](/docs/v2/sdk/events.md).
## 5. Next steps[](#5-next-steps "Direct link to 5. Next steps")
With these changes the widget should function the same way as it did before.
You will need to make some changes to your backend server code next, which are described in [this guide](/docs/v2/guides/upgrading-from-v1/backend-integration.md).
---
# Captcha Warning
Friendly Captcha may display a warning message that indicates billing problems or other misconfigurations. This page explains what each warning means and how to resolve it.
Not the website administrator?
These warnings are **only relevant to the website administrator**. If you're a visitor trying to use a website that shows a Friendly Captcha widget, you can **ignore the warning** and continue using the site normally. The warning is not related to anything you've done.
If you are the website administrator, you should **fix the warning as soon as possible or reach out to us to avoid service disruption**.
## Usage limit reached[](#usage_limit_reached "Direct link to Usage limit reached")

Your Friendly Captcha account has reached its monthly usage limit. The widget will continue to work and users will be able to complete the captcha, but you will see a warning until the issue is resolved. If the issue is not resolved, the widget may stop working entirely until you upgrade your plan.
**To fix this:**
* Check your usage in the [Friendly Captcha dashboard](https://app.friendlycaptcha.eu/dashboard/).
* Upgrade to a plan that offers more usage.
If you are having trouble upgrading your plan or need help, please [contact support](https://friendlycaptcha.com/support/).
## Commercial use detected[](#invalid_non_commercial_use "Direct link to Commercial use detected")

Your account is on the free plan, which is for non-commercial use only. Since you were previously on a paid plan, you likely need a commercial license.
The widget will continue to work and users will be able to complete the captcha, but you will see a warning until the issue is resolved. If the issue is not resolved, the widget may stop working entirely until you upgrade your plan.
**To fix this:**
* Open the [Friendly Captcha dashboard](https://app.friendlycaptcha.eu/dashboard/).
* Upgrade to a paid plan to get a commercial license and remove the warning.
If you believe your use qualifies as non-commercial, or you need help choosing a plan, please [contact support](https://friendlycaptcha.com/support/).
---
# Risk Intelligence
*Risk Intelligence* helps make decisions about the trustworthiness of users interacting with your services.
Friendly Captcha provides Risk Intelligence data for your website users without any friction or user interaction. This data includes various risk scores and signals that can help you assess the likelihood of fraudulent or malicious activity.
You can store and use this data to enhance your (existing) security systems, such as implementing risk-based authentication or fraud detection systems. For example, you may want to flag users with high risk scores for additional verification steps.
Risk Intelligence is available automatically for all customers on **Advanced** and **Enterprise** plans.
## How do I get Risk Intelligence data?[](#how-do-i-get-risk-intelligence-data "Direct link to How do I get Risk Intelligence data?")
The primary way to get Risk Intelligence data is through the Risk Intelligence API. This allows you to generate a risk intelligence token in the frontend and send it to your backend to retrieve the data. Follow the [**Getting Started**](/docs/v2/risk-intelligence/getting-started/.md) guide to learn how to use the Risk Intelligence API.
### Risk Intelligence on Captcha Challenge[](#risk-intelligence-on-captcha-challenge "Direct link to Risk Intelligence on Captcha Challenge")
Alternatively, you can get Risk Intelligence data when verifying a captcha challenge using the [siteverify API](/docs/v2/api/siteverify.md). This is a paid add-on that can be requested by [contacting support](https://friendlycaptcha.com/support/). You can read more about this in our [Risk Intelligence on Captcha Challenge](/docs/v2/risk-intelligence/on-captcha-challenge.md) document.
## Why is Risk Intelligence useful?[](#why-is-risk-intelligence-useful "Direct link to Why is Risk Intelligence useful?")
We wrote up example use cases for Risk Intelligence data in the [**Use Cases**](/docs/v2/risk-intelligence/use-cases.md) document.
In short, Risk Intelligence allows for more control and flexibility in how you handle user interactions for your applications. Perhaps low-risk users can interact with your services without friction, while high-risk users are required to complete additional verification steps. Additionally, you may want to collect additional data about high-risk users for auditing and monitoring purposes, or feed it into your existing fraud detection systems.
## What data is included in Risk Intelligence?[](#what-data-is-included-in-risk-intelligence "Direct link to What data is included in Risk Intelligence?")
Risk Intelligence consists of five modules, each providing different types of risk insights.
* **Risk Scores**: Combined risk scores that summarize the risk in different categories such as browser risk and network risk.
* **IP Intelligence**: Information about the IP address of the user, such as geolocation, ASN, and ASN type such as residential vs datacenter. IP Intelligence is sourced from [IP Trust](https://iptrust.co), a Friendly Captcha product.
* **Anonymization Detection**: Signals that indicate whether the user is using anonymization or masking services like VPNs or proxies. *This module will be launched in Q2 2026.*
* **Browser Identification**: Signals that help identify the browser and device being used by the user.
* **Bot Detection**: Information about what kind of automation or bot was detected.
## Risk Intelligence data format[](#risk-intelligence-data-format "Direct link to Risk Intelligence data format")
The Risk Intelligence data is returned as a JSON object in the `risk_intelligence` field of the API response. Below is an example of what the data might look like:
```
{
// Risk scores summarizing the assessment into scores per category (1-5) (Risk Scores module)
risk_scores: {
overall: 2, // Overall risk score combining all factors
network: 2, // Network-related risk based on IP, ASN, reputation, geolocation
browser: 1 // Browser-related risk based on user agent, automation traces, consistency
},
network: {
ip: "88.64.123.45", // IP address of the request, note this plain IP is *never* stored on our servers
// Autonomous System information (IP Intelligence module)
as: {
asn: 3209, // Autonomous System Number
name: "VODANET", // AS name/handle
company: "Vodafone GmbH", // Organization name that owns the ASN
description: "Provides mobile and fixed broadband and telecommunication services to consumers and businesses.",
domain: "vodafone.de", // Domain associated with the ASN
country: "DE", // Two-letter country code where ASN is registered
rir: "RIPE", // Regional Internet Registry that allocated the ASN
route: "88.64.0.0/12", // IP route in CIDR notation
type: "isp" // AS type (isp, hosting, mobile, etc.)
},
// Geographic location of the IP (IP Intelligence module)
geolocation: {
country: {
iso2: "DE", // Two-letter ISO 3166-1 alpha-2 code
iso3: "DEU", // Three-letter ISO 3166-1 alpha-3 code
name: "Germany", // Country name in English
name_native: "Deutschland", // Country name in native language
region: "Europe", // Major world region
subregion: "Western Europe", // More specific world region
currency: "EUR", // ISO 4217 currency code
currency_name: "Euro", // Full name of the currency
phone_code: "49", // International dialing code
capital: "Berlin" // Capital city
},
city: "Eschborn", // City name (empty string if unknown)
state: "Hessen" // State/region/province (empty string if unknown)
},
// Abuse contact information (IP Intelligence module)
abuse_contact: {
address: "Vodafone GmbH, Campus Eschborn, Duesseldorfer Strasse 15, D-65760 Eschborn, Germany", // Postal address
name: "Vodafone Germany IP Core Backbone", // Abuse contact name
email: "abuse.de@vodafone.com", // Abuse contact email
phone: "+49 6196 52352105" // Abuse contact phone
},
// IP anonymization detection (Anonymization Detection module)
anonymization: {
vpn_score: 2, // VPN likelihood score (1-5)
proxy_score: 1, // Proxy likelihood score (1-5)
tor: false, // Tor exit node detected
icloud_private_relay: false // iCloud Private Relay detected
}
},
client: {
// User-Agent HTTP header sent by the browser
header_user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
// Time zone from the browser (available when Browser Identification module is enabled)
time_zone: {
name: "Europe/Berlin", // IANA time zone identifier
country_iso2: "DE" // Country derived from time zone
},
// Detected browser information (Browser Identification module)
browser: {
id: "firefox", // Browser identifier
name: "Firefox", // Human-readable browser name
version: "146.0", // Browser version
release_date: "2026-01-28" // Release date of this version (YYYY-MM-DD)
},
// Browser rendering engine (Browser Identification module)
browser_engine: {
id: "gecko", // Engine identifier
name: "Gecko", // Human-readable engine name
version: "146.0" // Engine version
},
// Device information (Browser Identification module)
device: {
type: "desktop", // Device type (desktop, mobile, tablet, etc.)
brand: "", // Device manufacturer brand (empty for desktop)
model: "" // Device model name (empty for desktop)
},
// Operating system information (Browser Identification module)
os: {
id: "windows", // OS identifier
name: "Windows", // Human-readable OS name
version: "10" // OS version
},
// TLS/SSL signatures (Bot Detection module)
tls_signature: {
ja3: "d87a30a5782a73a83c1544bb06332780", // JA3 hash
ja3n: "28ecc2d2875b345cecbb632b12d8c1e0", // JA3N hash (normalized)
ja4: "t13d1516h2_8daaf6152771_02713d6af862" // JA4 signature
},
// Automation and bot detection (Bot Detection module)
automation: {
// Automation tool detection
automation_tool: {
detected: false, // Whether automation tool was detected
id: "", // Tool identifier (puppeteer, selenium, etc.)
name: "", // Human-readable tool name
type: "" // Tool type (webdriver, cdp, etc.)
},
// Known bot/crawler detection (good bots with public documentation)
known_bot: {
detected: false, // Whether a known bot was detected
id: "", // Bot identifier (googlebot, bingbot, etc.)
name: "", // Human-readable bot name
type: "", // Bot type (search, monitor, social, etc.)
url: "" // URL to bot documentation
}
}
}
}
```
The entire format is documented on the [Risk Intelligence Format](/docs/v2/risk-intelligence/format.md) page, where you can find detailed descriptions of all fields and example values.
## Next steps[](#next-steps "Direct link to Next steps")
If you want to use Risk Intelligence today, you can follow the [**Getting Started**](/docs/v2/risk-intelligence/getting-started/.md) guide.
If you want to learn more about how to use the Risk Intelligence data, check out the [**Use Cases**](/docs/v2/risk-intelligence/use-cases.md) document where we provide example implementations and ideas for how to use this data to enhance your security.
---
# Format
Risk Intelligence data is returned in a structured JSON format that includes various risk signals and scores. The exact structure of the data may vary depending on which modules you have enabled on your account (as of writing, all modules are included by default).
info
The structure of the Risk Intelligence data is subject to change. We always aim for backwards compatibility, but your validation and parsing logic should be flexible enough to handle new fields being added in the future.
## Structure Overview[](#structure-overview "Direct link to Structure Overview")
The Risk Intelligence data is organized into three high level sections:
```
{
risk_scores: { ... }, // Overall risk scores per category (1-5).
network: { ... }, // Information about the user's network and IP address.
client: { ... }, // Detected browser or bot information.
}
```
### Risk Scores[](#risk-scores "Direct link to Risk Scores")
The `risk_scores` section provides a summary of the risk assessment in the form of scores for different categories. Each score is an integer value between 1 and 5, where 1 indicates very low risk and 5 indicates very high risk.
The available risk scores include:
* `overall`: An overall risk score that combines all available signals into a single score.
* `network`: A risk score based on network-related signals such as IP reputation, ASN information, and geolocation. You can interpret this score as, based on the user's network characteristics, how likely the request is to be automated, fraudulent or malicious.
* `browser`: A risk score based on browser-related signals such as user agent, browser identification, bot identification and other client-side characteristics. This score helps assess the likelihood of the request being automated or coming from a suspicious client.
JSON Example
```
"risk_scores": {
"overall": 2,
"network": 2,
"browser": 1
}
```
### Network Information[](#network-information "Direct link to Network Information")
info
The IP address is never stored on our servers in an unhashed format. We encode it into the risk intelligence token so that we can pass it on to you.
You can compare this IP address to the one you see in your server logs to correlate the risk intelligence data with specific requests.
The `network` section provides detailed information about the user's network and IP address. This includes:
* `ip`: The IP address of the user when the risk intelligence data was gathered on the frontend.
* `as`: Information about the Autonomous System (AS) associated with the user's IP address, including ASN, AS name, company, description, domain, country, RIR, route and type.
* `geolocation`: Geographic information about the user's IP address, including city, region, country.
* `anonymization`: Signals indicating whether the user is using anonymization services such as VPNs or proxies, including VPN detection, proxy detection, Tor detection.
JSON Example
```
"network": {
"ip": "88.64.123.45"
"as": {
"asn": 3209,
"name": "VODANET",
"company": "Vodafone GmbH",
"description": "Provides mobile and fixed broadband and telecommunication services to consumers and businesses.",
"domain": "vodafone.de",
"country": "DE",
"rir": "RIPE",
"route": "88.64.0.0/12",
"type": "isp"
},
"geolocation": {
"country": {
"iso2": "DE",
"iso3": "DEU",
"name": "Germany",
"name_native": "Deutschland",
"region": "Europe",
"subregion": "Western Europe",
"currency": "EUR",
"currency_name": "Euro",
"phone_code": "49",
"capital": "Berlin"
},
"city": "Eschborn",
"state": "Hessen"
},
"anonymization": {
"vpn_score": 2,
"proxy_score": 1,
"tor": false,
"icloud_private_relay": false
}
}
```
### Client Information[](#client-information "Direct link to Client Information")
The `client` section provides information about the user's client, which can be a browser or a bot. This includes:
* `browser`: Detected browser information, including browser name, version, rendering engine, device type, brand and model, and operating system.
* `tls_signature`: TLS/SSL-derived information, including JA3 and JA4 signatures. These signatures can be used to identify specific clients and detect anomalies.
* `automation`: Signals related to automation and bot detection, including detected automation tools and bot types.
JSON Example
```
"client": {
"header_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
"time_zone": {
"name": "Europe/Berlin",
"country_iso2": "DE"
},
"browser": {
"id": "firefox",
"name": "Firefox",
"version": "146.0",
"release_date": "2026-01-28"
},
"browser_engine": {
"id": "gecko",
"name": "Gecko",
"version": "146.0"
},
"device": {
"type": "desktop",
"brand": "",
"model": ""
},
"os": {
"id": "windows",
"name": "Windows",
"version": "10"
},
"tls_signature": {
"ja3": "d87a30a5782a73a83c1544bb06332780",
"ja3n": "28ecc2d2875b345cecbb632b12d8c1e0",
"ja4": "t13d1516h2_8daaf6152771_02713d6af862"
},
"automation": {
"automation_tool": {
"detected": false,
"id": "",
"name": "",
"type": ""
},
"known_bot": {
"detected": false,
"id": "",
"name": "",
"type": "",
"url": ""
}
}
}
```
## Field Reference[](#field-reference "Direct link to Field Reference")
A comprehensive breakdown of all fields in the Risk Intelligence data format.
### Risk Scores[](#risk-scores-1 "Direct link to Risk Scores")
`risk_scores` - Risk assessment scores
Risk scores summarize the entire risk intelligence assessment into scores per category. Each score is an integer from 0-5.
| Value | Meaning |
| ----- | ------------------ |
| `0` | Unknown or missing |
| `1` | Very low risk |
| `2` | Low risk |
| `3` | Medium risk |
| `4` | High risk |
| `5` | Very high risk |
info
**Why are risk scores not a percentage or a decimal score?** We use a 1-5 integer scale to make it easier for you to set thresholds and make decisions based on the scores. A percentage or decimal score can give a false sense of precision, while in reality the underlying data and signals are often noisy and not precise enough to justify that level of granularity (ie what is the difference between 81% and 82% risk?).
The 1-5 scale provides a more actionable and interpretable way to use the risk scores in your decision-making processes.
`overall` - Overall risk score
**Type:** Integer (0-5)
**Description:** A combined risk score that aggregates all available signals into a single assessment.
**Example:**
```
"overall": 2
```
`network` - Network risk score
**Type:** Integer (0-5)
**Description:** Risk score based on network-related signals such as IP reputation, ASN information, geolocation, past abuse from this network, and other network characteristics. Indicates the likelihood of automation or malicious activity based on the network.
**Example:**
```
"network": 2
```
`browser` - Browser risk score
**Type:** Integer (0-5)
**Description:** Risk score based on browser-related signals such as user agent consistency, automation traces, past abuse, and browser characteristics. Indicates the likelihood of automation, malicious activity, or browser spoofing.
**Example:**
```
"browser": 1
```
### Network[](#network "Direct link to Network")
`network` - Network and IP information
Contains information about the user's network, IP address, and related characteristics.
`ip` - Client IP address
**Type:** String
**Description:** The IP address used when requesting the challenge. You can compare this IP with the one that is submitting to your backend. Note that IP addresses can change mid-session, for example when users are on mobile networks or using certain ISPs - so do create a way for users to update the IP address associated with a session in your system.
**Example:**
```
"ip": "88.64.4.22"
```
`as` - Autonomous System information
**Type:** Object or `null`
**Description:** Information about the Autonomous System that owns the IP address.
`number` - ASN
**Type:** Integer
**Description:** Autonomous System Number (ASN) identifier.
**Example:**
```
"number": 3209
```
`name` - AS name
**Type:** String
**Description:** Short name or handle of the autonomous system.
**Example:**
```
"name": "VODANET"
```
`company` - Company name
**Type:** String
**Description:** Organization name that owns the ASN.
**Example:**
```
"company": "Vodafone GmbH"
```
`description` - Company description
**Type:** String
**Description:** Short description of the company that owns the ASN.
**Example:**
```
"description": "Provides mobile and fixed broadband and telecommunication services to consumers and businesses."
```
`domain` - Company domain
**Type:** String
**Description:** Domain name associated with the ASN.
**Example:**
```
"domain": "vodafone.de"
```
`country` - ASN country
**Type:** String
**Description:** Two-letter ISO 3166-1 alpha-2 country code where the ASN is registered.
**Example:**
```
"country": "DE"
```
`rir` - Regional Internet Registry
**Type:** String
**Description:** RIR that allocated the ASN.
**Possible Values:**
* `"ARIN"` - American Registry for Internet Numbers (North America)
* `"RIPE"` - Réseaux IP Européens (Europe, Middle East, Central Asia)
* `"APNIC"` - Asia-Pacific Network Information Centre
* `"LACNIC"` - Latin America and Caribbean Network Information Centre
* `"AFRINIC"` - African Network Information Centre
**Example:**
```
"rir": "RIPE"
```
`route` - IP route
**Type:** String
**Description:** IP route in CIDR notation associated with the ASN.
**Example:**
```
"route": "88.64.0.0/12"
```
`type` - AS type
**Type:** String
**Description:** Type classification of the autonomous system.
**Possible Values:**
* `"isp"` - Internet Service Provider
* `"mobile"` - Mobile network operator
* `"government"` - Government organization
* `"hosting"` - Hosting/data center provider
* `"education"` - Educational institution
* `"individual"` - Individual/personal ASN
* `"business"` - Business/corporate network
* `"other"` - Other/uncategorized
**Example:**
```
"type": "isp"
```
`geolocation` - Geographic location
**Type:** Object or `null`
**Description:** Geographic location information for the IP address.
`country` - Country information
**Type:** Object
**Description:** Detailed information about the country.
`iso2` - ISO 3166-1 alpha-2 code
**Type:** String
**Description:** Two-letter country code.
**Example:**
```
"iso2": "DE"
```
`iso3` - ISO 3166-1 alpha-3 code
**Type:** String
**Description:** Three-letter country code.
**Example:**
```
"iso3": "DEU"
```
`name` - Country name
**Type:** String
**Description:** English name of the country.
**Example:**
```
"name": "Germany"
```
`name_native` - Native country name
**Type:** String
**Description:** Country name in its native language.
**Example:**
```
"name_native": "Deutschland"
```
`region` - Geographic region
**Type:** String
**Description:** Major world region.
**Example:**
```
"region": "Europe"
```
`subregion` - Geographic subregion
**Type:** String
**Description:** More specific world region.
**Example:**
```
"subregion": "Western Europe"
```
`currency` - Currency code
**Type:** String
**Description:** ISO 4217 currency code.
**Example:**
```
"currency": "EUR"
```
`currency_name` - Currency name
**Type:** String
**Description:** Full name of the primary currency used in the country.
**Example:**
```
"currency_name": "Euro"
```
`phone_code` - Country phone code
**Type:** String
**Description:** International dialing code.
**Example:**
```
"phone_code": "49"
```
`capital` - Capital city
**Type:** String
**Description:** Name of the capital city.
**Example:**
```
"capital": "Berlin"
```
`city` - City name
**Type:** String
**Description:** City of the IP address. Empty string if unknown.
**Example:**
```
"city": "Eschborn"
```
`state` - State/region/province
**Type:** String
**Description:** State, region, or province of the IP address. Empty string if unknown.
**Example:**
```
"state": "Hessen"
```
`abuse_contact` - Abuse contact information
**Type:** Object or `null`
**Description:** Contact information for reporting network abuse.
`address` - Postal address
**Type:** String
**Description:** Postal address of the abuse contact.
**Example:**
```
"address": "Vodafone GmbH, Campus Eschborn, Duesseldorfer Strasse 15, D-65760 Eschborn, Germany"
```
`name` - Contact name
**Type:** String
**Description:** Name of the abuse contact person or team.
**Example:**
```
"name": "Vodafone Germany IP Core Backbone"
```
`email` - Contact email
**Type:** String
**Description:** Abuse contact email address.
**Example:**
```
"email": "abuse.de@vodafone.com"
```
`phone` - Contact phone
**Type:** String
**Description:** Abuse contact phone number. This can be in various formats, but often includes the country code. Note that not all abuse contacts provide a phone number.
**Example:**
```
"phone": "+49 6196 52352105"
```
`anonymization` - Anonymization detection
**Type:** Object or `null`
**Description:** Detection of VPNs, proxies, and anonymization services.
`vpn_score` - VPN detection score
**Type:** Integer (0-5)
**Description:** Likelihood that a VPN is being used. Higher scores indicate stronger evidence of VPN usage.
**Example:**
```
"vpn_score": 2
```
`proxy_score` - Proxy detection score
**Type:** Integer (0-5)
**Description:** Likelihood that the user is connecting through a proxy server. Higher scores indicate stronger evidence of proxy usage.
**Example:**
```
"proxy_score": 1
```
`tor` - Tor exit node
**Type:** Boolean
**Description:** Whether the IP is a Tor exit node.
**Example:**
```
"tor": false
```
`icloud_private_relay` - iCloud Private Relay
**Type:** Boolean
**Description:** Whether the IP is from iCloud Private Relay.
**Example:**
```
"icloud_private_relay": false
```
### Client[](#client "Direct link to Client")
`client` - User agent and device information
Contains information about the client device, browser, and operating system.
`header_user_agent` - User-Agent header
**Type:** String
**Description:** The User-Agent HTTP header value.
**Example:**
```
"header_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:146.0) Gecko/20100101 Firefox/146.0"
```
`time_zone` - Time zone information
**Type:** Object or `null`
**Description:** IANA time zone information from the browser.
`name` - IANA time zone
**Type:** String
**Description:** IANA time zone name reported by the browser.
**Example:**
```
"name": "America/New_York"
```
`country_iso2` - Country from time zone
**Type:** String
**Description:** Two-letter ISO 3166-1 alpha-2 country code derived from the time zone. Returns `"XU"` if timezone is missing or cannot be mapped to a country (e.g., "Etc/UTC").
**Example:**
```
"country_iso2": "US"
```
`browser` - Browser information
**Type:** Object or `null`
**Description:** Detected browser details.
`id` - Browser identifier
**Type:** String
**Description:** Unique browser identifier. Empty string if browser could not be identified.
**Possible Values:**
* `"chrome"` - Chrome
* `"chrome_android"` - Chrome for Android
* `"edge"` - Microsoft Edge
* `"firefox"` - Firefox
* `"firefox_android"` - Firefox for Android
* `"ie"` - Internet Explorer
* `"oculus"` - Quest Browser (Oculus)
* `"opera"` - Opera
* `"opera_android"` - Opera for Android
* `"safari"` - Safari
* `"safari_ios"` - Safari on iOS
* `"samsunginternet_android"` - Samsung Internet for Android
* `"webview_android"` - WebView on Android
* `"webview_ios"` - WebView on iOS
* `""` - Unknown
**Example:**
```
"id": "firefox"
```
`name` - Browser name
**Type:** String
**Description:** Human-readable browser name. Empty string if browser could not be identified.
**Example:**
```
"name": "Firefox"
```
`version` - Browser version
**Type:** String
**Description:** Browser version number. Assumed to be the most recent release matching the signature if exact version unknown. Empty if unknown.
**Example:**
```
"version": "146.0"
```
`release_date` - Version release date
**Type:** String
**Description:** Release date of the browser version in "YYYY-MM-DD" format. Empty string if unknown.
**Example:**
```
"release_date": "2026-01-28"
```
`browser_engine` - Rendering engine information
**Type:** Object or `null`
**Description:** Browser rendering engine details.
`id` - Engine identifier
**Type:** String
**Description:** Unique rendering engine identifier. Empty string if engine could not be identified.
**Possible Values:**
* `"blink"` - Blink (Chromium-based browsers like Chrome, Edge, Opera)
* `"edgehtml"` - EdgeHTML (legacy Microsoft Edge, no longer developed)
* `"gecko"` - Gecko (Firefox)
* `"presto"` - Presto (legacy Opera, no longer developed)
* `"trident"` - Trident (Internet Explorer)
* `"v8"` - V8 JavaScript engine
* `"webkit"` - WebKit (Safari)
* `""` - Unknown
**Example:**
```
"id": "gecko"
```
`name` - Engine name
**Type:** String
**Description:** Human-readable engine name. Empty string if engine could not be identified.
**Example:**
```
"name": "Gecko"
```
`version` - Engine version
**Type:** String
**Description:** Rendering engine version. Assumed to be the most recent release matching the signature if exact version unknown. Empty if unknown.
**Example:**
```
"version": "146.0"
```
`device` - Device information
**Type:** Object or `null`
**Description:** Device type and details.
`type` - Device type
**Type:** String
**Description:** Type of device.
**Possible Values:**
* `"desktop"` - Desktop device
* `"mobile"` - Mobile phone
* `"tablet"` - Tablet device
* `"tv"` - TV device
* `"console"` - Game console (PlayStation, Xbox, etc.)
* `"wearable"` - Wearable device (smartwatch, fitness tracker, etc.)
* `"xr"` - Extended reality device (VR headset, AR glasses, etc.)
* `"unknown"` - Unknown device type
**Example:**
```
"type": "desktop"
```
`brand` - Device brand
**Type:** String
**Description:** Manufacturer brand.
**Example:**
```
"brand": "Apple"
```
`model` - Device model
**Type:** String
**Description:** Device model name.
**Example:**
```
"model": "iPhone 17"
```
`os` - Operating system information
**Type:** Object or `null`
**Description:** Detected operating system details.
`id` - OS identifier
**Type:** String
**Description:** Unique operating system identifier. Empty string if OS could not be identified.
**Possible Values:**
* `"windows"` - Microsoft Windows
* `"macos"` - Apple macOS
* `"linux"` - Linux
* `"android"` - Android
* `"ios"` - Apple iOS
* `"ipados"` - Apple iPadOS
* `"chromeos"` - ChromeOS
* `""` - Unknown
**Example:**
```
"id": "windows"
```
`name` - OS name
**Type:** String
**Description:** Human-readable operating system name. Empty string if OS could not be identified.
**Example:**
```
"name": "Windows"
```
`version` - OS version
**Type:** String
**Description:** Operating system version number.
**Example:**
```
"version": "10"
```
`tls_signature` - TLS/SSL signatures
**Type:** Object or `null`
**Description:** TLS client hello signatures (also called TLS fingerprints) derived from the TLS handshake between the client and server.
`ja3` - JA3 signature
**Type:** String
**Description:** JA3 TLS fingerprint hash.
**Example:**
```
"ja3": "d87a30a5782a73a83c1544bb06332780"
```
`ja3n` - JA3N signature
**Type:** String
**Description:** JA3N TLS fingerprint hash.
**Example:**
```
"ja3n": "28ecc2d2875b345cecbb632b12d8c1e0"
```
`ja4` - JA4 signature
**Type:** String
**Description:** JA4 TLS fingerprint.
**Example:**
```
"ja4": "t13d1516h2_8daaf6152771_02713d6af862"
```
`automation` - Automation and bot detection
**Type:** Object or `null`
**Description:** Information about detected automation and bots.
`automation_tool` - Automation tool detection
**Type:** Object
**Description:** Detected automation tool information. Note that many automation tools are designed to mimic real browsers and may not be detected, so a value of `detected: false` does not necessarily mean that no automation is being used. The browser risk score can help assess the likelihood of automation even when specific tools are not detected.
`detected` - Detection flag
**Type:** Boolean
**Description:** Whether an automation tool was detected.
**Example:**
```
"detected": false
```
`id` - Tool identifier
**Type:** String
**Description:** Automation tool identifier. Empty if no tool detected.
**Examples:** `"puppeteer"`, `"playwright"`, `"webdriver"`
**Example:**
```
"id": "playwright"
```
`name` - Tool name
**Type:** String
**Description:** Human-readable tool name. Empty if no tool detected.
**Examples:** `"Puppeteer"`, `"Playwright"`, `"WebDriver"`
**Example:**
```
"name": "Playwright"
```
`type` - Tool type
**Type:** String
**Description:** Type of automation tool. Empty if no tool detected.
**Possible Values:**
* `"browser_automation"` - Browser automation tool (e.g., Puppeteer, WebDriver, Playwright)
* `""` - No tool detected
Note
**Example:**
```
"type": "browser_automation"
```
`known_bot` - Known bot detection
**Type:** Object
**Description:** Detected known bot information. Known bots have public documentation about their identity and purpose.
`detected` - Detection flag
**Type:** Boolean
**Description:** Whether a known bot was detected.
**Example:**
```
"detected": false
```
`id` - Bot identifier
**Type:** String
**Description:** Bot identifier. Empty if no bot detected.
**Possible Values (Search Engines):**
* `"GoogleBot"` - Google search crawler
* `"BingBot"` - Microsoft Bing search crawler
* `"YahooBot"` - Yahoo search crawler
* `"DuckDuckBot"` - DuckDuckGo search crawler
* `"BaiduBot"` - Baidu search crawler (China)
* `"YandexBot"` - Yandex search crawler (Russia)
* `"360Bot"` - 360 search crawler (China)
* `"SogouBot"` - Sogou search crawler (China)
* `"SeznamBot"` - Seznam search crawler (Czech Republic)
**Possible Values (Social Media):**
* `"FacebookBot"` - Facebook link preview crawler
* `"TwitterBot"` - Twitter/X link preview crawler
* `"LinkedInBot"` - LinkedIn link preview crawler
* `"PinterestBot"` - Pinterest crawler
* `"DiscordBot"` - Discord link preview bot
* `"TelegramBot"` - Telegram link preview bot
* `"WhatsAppBot"` - WhatsApp link preview bot
**Possible Values (AI/LLM):**
* `"OAI-SearchBot"` - OpenAI web search bot
* `"GPTBot"` - OpenAI GPT crawler
* `"ChatGPT-User"` - ChatGPT user-initiated request
* `"ClaudeBot"` - Anthropic Claude crawler
* `"PerplexityBot"` - Perplexity AI crawler
* `"Perplexity-User"` - Perplexity user-initiated request
* `"Google-Extended"` - Google AI/LLM crawler
* `"Applebot-Extended"` - Apple AI/LLM crawler
**Possible Values (SEO/Crawlers):**
* `"SemrushBot"` - Semrush SEO crawler
* `"AhrefsBot"` - Ahrefs SEO crawler
* `"MJ12Bot"` - Majestic SEO crawler
* `"DotBot"` - Moz/OpenSiteExplorer crawler
* `"RogerBot"` - Moz/RogerBot crawler
* `"PetalBot"` - Huawei Petal search crawler
* `"ExaBot"` - Exalead crawler
* `"ProximicBot"` - Comscore Proximic crawler
**Possible Values (Monitoring):**
* `"UptimeRobot"` - Uptime monitoring service
* `"PingdomBot"` - Pingdom monitoring service
* `"GTmetrixBot"` - GTmetrix performance monitoring
* `"Site24x7Bot"` - Site24x7 monitoring service
* `"NewRelicBot"` - New Relic monitoring service
* `"MonitisBot"` - Monitis monitoring service
**Possible Values (Other):**
* `"AppleBot"` - Apple web crawler
* `"AmazonBot"` - Amazon web crawler
* `"MSNBot"` - Microsoft MSN crawler (legacy)
* `"InternetArchiveBot"` - Internet Archive Wayback Machine crawler
* `"UnknownBot"` - Detected as bot but specific identity unknown
* `""` - No bot detected
**Example:**
```
"id": "GoogleBot"
```
`name` - Bot name
**Type:** String
**Description:** Human-readable bot name. Empty if no bot detected.
**Examples:** `"Googlebot"`, `"Bingbot"`, `"ChatGPT"`
**Example:**
```
"name": ""
```
`type` - Bot type
**Type:** String
**Description:** Bot type classification. Empty if no bot detected.
**Possible Values:**
* `"search_engine"` - Search engine crawler
* `"social"` - Social media crawler/link preview bot
* `"crawler"` - General web crawler (SEO, archival, etc.)
* `"monitoring"` - Monitoring/uptime check service
* `"ai"` - AI bot (generic, when specific type unknown)
* `"ai_crawler"` - AI model training/data collection crawler
* `"ai_user_initiated"` - AI assistant responding to user request
* `"ai_agent"` - Autonomous AI agent
* `""` - No bot detected
**Example:**
```
"type": "search_engine"
```
`url` - Bot documentation URL
**Type:** String
**Description:** Link to bot documentation. Empty if no bot detected.
**Example:**
```
"url": "https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers"
```
---
# Getting Started with Risk Intelligence
There are two parts to getting Risk Intelligence data. First, you generate a Risk Intelligence ***token*** in your project's front-end. Second, you use that token to retrieve Risk Intelligence ***data*** from the Friendly Captcha API via your project's back-end.

The request flow for Risk Intelligence, showing token generation and data retrieval.
With the data in your project's back-end, you can make Risk Intelligence-informed decisions about how to handle user actions in your project---e.g., require another authentication step, or send a suspicious account activity email. To get a sense of some possibilities, take a look at [the list of use cases](/docs/v2/risk-intelligence/use-cases.md).
To get started, follow these steps:
1. [Create a sitekey and API key.](/docs/v2/risk-intelligence/getting-started/setup.md)
2. [Generate a Risk Intelligence token in your project's front-end.](/docs/v2/risk-intelligence/getting-started/generate.md)
3. [Retrieve Risk Intelligence data in your project's back-end.](/docs/v2/risk-intelligence/getting-started/retrieve.md)
---
# Generate a Risk Intelligence token
You can generate a token directly using [JavaScript](#javascript), or you can use an [HTML element](#html) to automatically generate a token and embed it in a `