Skip to main content

API Integration

You can set up VerifiedVisitors manually with your tech stack of choice by integrating with our access control API directly, if our pre-built integrations don't work for you.

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.

Clientside considerations

VerifiedVisitors identifies visitors using a unique visitor ID. Although these are determined using various signals internally, we recommend using first-party cookies to track visitor IDs more accurately.

We use hCaptcha for CAPTCHA challenges at no additional cost to you. This involves serving dynamic HTML content as part of the response as well as capturing and forwarding results to VerifiedVisitors. This is detailed below.

We also have an optional client-side JS agent which collects information about client user-agents automatically. We recommend deploying/serving this alongside any HTML content.

Integration overview

You can integrate with VerifiedVisitors using our pre-built modules or manually using our API.

View a diagram outlining some integration strategies.

Our pre-built integrations currently support Cloudflare and AWS CloudFront.

For a working example, see our CloudFront Lambda integration.

Manual integrations involve developing custom module(s) to integrate with your application(s). For example, as NodeJS Express or PHP Laravel middleware.

The module will be responsible for querying the VerifiedVisitors access control API with details about the request, interpreting the response, and serving a mitigation or allowing the request through, while also setting the visitor-id cookie.

A mitigation response can be to either serve CAPTCHA, or block the request outright.

Access control should be enforced as early as possible in the request processing lifecycle.


Remember, it's possible for visitors to be issued a mitigation for any request that's covered by the integration, including programmatic requests to API endpoints and form actions, such as POST requests, and is not limited to GETs for HTML pages, which would otherwise allow for automatically presenting a mitigation such as CAPTCHA by returning it as the response.

This can be especially important for SPAs, where a lot of the interaction is done via programmatic requests. In such cases you might decide to display the mitigation programatically in the frontend, or reload the page, so that the mitigation response from the integration is displayed automatically by the browser.

It's worth noting that because some of our threat detection methods work by observing behavioural aspects, some mitigating rules may be triggered part-way through a visitor's session. For example, a visitor may be blocked while submitting a form or triggering an action that sends a request. This can lead to invisible failures or unexpected behaviour if not handled correctly by the frontend.

Request processing lifecycle

Access control enforcement involves the following high level steps:

  1. Read the visitor-id value from the cookie, if present
  2. Parse information from the request for use with the access control API
  3. Query the access control API, getting back a decision (allow or a mitigation)
  4. Set the visitor-id cookie in the response headers, if necessary
  5. Prepare a mitigation response if the decision is to not allow the request
  6. Send the mitigation response or proceed with the request processing lifecycle

We highly recommend setting a cookie as part of any generated response. For example: set-cookie: vv_vid=VISITOR_ID; path=/; SameSite=Lax; domain=ROOT_DOMAIN; expires=EXPIRY_DATE.

The access control API returns a visitor ID and recommended cookie domain value only when necessary.


The HttpOnly option must not be set on the cookie so that the client-side JavaScript agent can read it.

Querying the access control API

The access control API is served from and the spec can be found here.


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.

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 comprises of non-sensitive header values, a header key enumeration, and basic details such as the IP address, URI, and VerifiedVisitors Visitor ID.

The response includes the access control decision (allow, captcha, block), as well as a new visitor ID and visitor ID cookie domain (if any).

Captcha decisions will include a hCaptcha site key required when serving the challenge.

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

For an example of this, see our CloudFront Lambda integration. The request is prepared and sent in the query function.


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.

Serving mitigations & CAPTCHA

For a block decision, it is sufficient to return a 401 with optional content.

Serving CAPTCHA challenges is a little more involved. In this case, the module must send a HTML response with the embedded challenge, and forward any responses to the challenge to the access control API.

This process

  1. A captcha decision is returned from the API, including the hCaptcha key
  2. The access control module returns the prepared challenge HTML with the key injected
  3. The visitor completes the CAPTCHA form, sending the result back to the origin with the CAPTCHA token and visitor ID cookie
  4. The access control module extracts the CAPTCHA token from the request and includes it in the call to the access control 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.

For an example of this in action, see our CloudFront Lambda integration. The challenge HTML is generated and sent in the serveCAPTCHA function, and token extraction is performed in the parse function when processing the response.

View a sequence diagram of the CAPTCHA flow.

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


/* 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 ='

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

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


Client-side JavaScript agent

We recommend serving our JS agent along with any HTML content to improve threat detection and visitor categorisation.

Please see the JS-agent docs for more info.

Example call

curl -XPOST \
-H 'Content-Type: application/json' \
-H 'Authorization: bearer API_TOKEN' \
-d @- <<'EOF'
"timestamp": 1683643245768,
"visitorId": {
"vid": "9a20079bc4597fce683c583c18797156",
"ip": "",
"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ Safari/537.36"
"host": "",
"uri": "",
"method": "GET",
"headers": ["user-agent", "accept", "accept-encoding"],
"connection": "keep-alive",
"referer": "",
"origin": "string",
"pragma": "no-cache",
"xForwardedFor": ",",
"xForwardedProto": "https",
"xRequestedWith": "XMLHttpRequest",
"xRealIp": "",
"trueClientIp": "",
"via": "1.1 vegur",
"accept": "text/html",
"acceptEncoding": "gzip",
"acceptLanguage": "en-US",
"acceptCharset": "string",
"contentType": "text/html",
"contentLength": "30012",
"cacheControl": "max-age=604800",
"from": "[email protected]",
"cc": "GB",
"hCaptchaToken": "10000000-aaaa-bbbb-cccc-000000000001"

"action": "captcha",
"captchaSiteKey": "10000000-ffff-ffff-ffff-000000000001",
"visitorId": "9a20079bc4597fce683c583c18797156",
"rootDomain": ""
"countryCode": "US",