Skip to main content

API Integration

If one of our pre-built integration modules aren't suitable, you can write your own by using with the VerifiedVisitors API directly.

This API returns an access control decision for a given request. Each visitor is treated according to its risk and the policies you've defined in VerifiedVisitors. For example, risky behaviour such as ATO can be hard blocked, or served CAPTCHA first to prevent false positives.

All access control decisions can be viewed and audited in the VerifiedVisitors command & control view.

Integration
Architecture

Overview


Server-side integrations work in conjunction with the required JS agent to display challenge pages when necessary.

The integration sits between the visitor and the origin (e.g, in a proxy or app authentication middleware), forwards parts of the request to VerifiedVisitors, and enforces any access control decisions by forwarding headers and body payloads to the client for the JS agent to use. Here's a diagram outlining some integration strategies.

VerifiedVisitors assigns a unique visitor ID to each visitor. This should be persisted using a cookie, and is read by the JS agent to correctly display challenges and associate client-side telemetry with the visitor.

info

Remember, automation/bot detection isn't always immediate, and a visitor may be issued a mitigation any time after they've landed on the page, including for API calls.

Programatic calls are monitored by the JS agent in order to automatically display challenge pages for them. This behaviour can be configured. See JS Agent for more info.

Server-side integration


Server-side integrations intercept visitor requests and call the VerifiedVisitors API to make access control decisions. It should sit between the visitor and the protected resource.

For a working example, see our CloudFront Lambda or Cloudflare Worker implementations.

Request processing overview

To protect your site, the server-side module should be installed into an appropriate point in the request lifecycle, and perform the following steps:

  1. Read the visitor-id value from the cookie if present
  2. Package information from the request to send to the VerifiedVisitors API
  3. Query the API and parse the response for an access-control decision
  4. Set any VerifiedVisitors headers and/or pass-through any content from the API
  5. Set the visitor-id cookie in the response headers if necessary
  6. Send the mitigation response or allow the request through

In order for the JS agent to work properly and display challenge pages, the visitor ID given by the VerifiedVisitors API must be set in a cookie named vv_vid for every request:

`set-cookie: vv_vid=VISITOR_ID; path=/; SameSite=Lax; domain=ROOT_DOMAIN; expires=EXPIRY_DATE`.
caution

The HttpOnly option must not be set on the cookie in order for the client-side JavaScript agent to read it.

Querying the access control API

The access control API spec can be found here and is accessible at the following URL: https://api.verifiedvisitors.com/vac/verify.

We recommend including as many of the fields as possible, but the following minimal example is enough to get started:

curl -XPOST https://api.verifiedvisitors.com/vac/verify \
-H 'Content-Type: application/json' \
-H 'Authorization: bearer API_TOKEN' \
-d @- <<'EOF'
{
"timestamp": 1683643245768,
"visitorId": {
"vid": "9a20079bc4597fce683c583c18797156",
"ip": "203.0.113.0",
"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
},
"host": "your-website.com",
"uri": "https://your-website.com/example?param=value",
"method": "GET",
}
EOF

{
"action": "captcha",
"visitorId": "9a20079bc4597fce683c583c18797156",
"rootDomain": "your-website.com"
"countryCode": "US",
"htmlBody": "<html>..."
}

A more detailed example can be found in the spec.

info

The host value is used to associate requests with a site in VerifiedVisitors. This value must match the domain name specified when setting up a site.

API calls are authenticated via HTTP bearer auth: Authorization: bearer API_TOKEN. Tokens can be generated from the VerifiedVisitors profile settings page.

When querying the API, the module should include as much of the requested information as possible in order to improve threat detection and visitor categorisation in VerifiedVisitors.

This information includes the IP, UA, non-sensitive header values as well as a list of included headers, and the URL. We recommend including the querystring portion with the URL, though this is optional. The VisitorID from the cookie must be included with the request if one exists.

Response include the access control decision (allow, captcha, js_challenge, or block), as well as a visitor ID and cookie domain (if any). A htmlBody field is also included for use when responding with a mitigation to HTML GET requests.

caution

Your integration should account for cases where the API unexpectedly returns a non-OK status code or takes too long to respond. To avoid disruption, we recommend failing-open in these cases and setting a timeout of around 1 second.

Returning a response and enforcing mitigations

Allow decisions should pass-through the request to the origin or next step in the request lifecycle. Allowed requests must still ensure that the visitor ID cookie is set.

For mitigating decisions, the integration should return an error code (e.g. 403) to block the request. Challenge responses must additionally set the vv-action header to the appropriate action (i.e. vv-action: captcha or vv-action: js_challenge).

The API includes a htmlBody field with challenge actions containing a HTML page with the JS agent loaded and configured to display the challenge immediately. This should be included in the response to the client for GET requests to HTML resources.

Challenges are presented by the JS agent within an iframe. For this to work, the JS agent must be loaded in the client's browser, and the server-side integration must include a vv-action header in the response.


legacy/in-line challenge presentation

Challenges may be served directly by the server-side integration without requiring the JS agent.

caution

This approach has been deprecated in favour of the JS agent driven iframe approach, but is included here for legacy reasons.

To serve and process challenges, the integration must send a HTML response with the embedded challenge, and forward any responses for the challenge to the API.

  1. A captcha decision is returned from the API, including the hCaptcha key
  2. The integration returns the prepared challenge page along with the key
  3. The visitor completes the CAPTCHA, sending the result back to the origin with the CAPTCHA token and visitor ID
  4. The integration parses and extracts the CAPTCHA token from the request and includes it in the call to the API in the hCaptchaToken property.
  5. At this point, the CAPTCHA result is associated with the Visitor ID and if the attempt was successful, further requests should yield allow decisions.
  6. The CAPTCHA page reloads or navigates to allow the visitor to continue

View a sequence diagram of this CAPTCHA flow.

Here's an exmaple of a CAPTCHA challenge page templated using JavaScript:

const siteKey = 'RETURNED_FROM_ACCCESS_CONTROL_API'

/* This should be read from subsequent requests and sent to the access control API */
const captchaResponseField = 'h-captcha-response'

/* The action to take upon completing the CAPTCHA. For POST requests we navigate
backwards so that the user can re-attempt the form. */
const onComplete =
originalMethod === 'POST'
? 'history.go(-1)'
: 'location.href = location.search'

const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>Verify</title>
<script
src="https://hcaptcha.com/1/api.js?recaptchacompat=off"
async defer
></script>
<style>
body {
margin: 0;
font-family: sans-serif;
}
.container {
margin-top: 200px;
display: flex;
flex-direction: column;
align-items: center;
gap: 45px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>We've detected something unusual about your visit.</h1>
<p>Please complete the CAPTCHA to continue.</p>
</header>
<form id="challenge-form" action="" method="POST">
<div
class="h-captcha"
data-sitekey="${siteKey}"
data-callback="onComplete"
></div>
</form>
<script>
async function onComplete(response) {
const body = new URLSearchParams({
'${captchaResponseField}': response
})

const r = await fetch('/', {
method: 'POST',
body,
})

${onComplete}
}
</script>
</div>
</body>
</html>`