Proof-of-Life · Developers · Early access

Integrate Proof-of-Life

PoL Gate follows the same pattern as reCAPTCHA, hCaptcha and Turnstile: a widget on the page, a token in the form, one server call to check it. Step-up, recovery and activity verification build on the same token and the same check.

Proof-of-Life is in early access. Site keys (pk_...) and secret keys (sk_...) are issued to early-access partners. Request access. Detection thresholds are still being calibrated on real devices, so field names are stable but default thresholds may change before general availability.

Quick start

  1. Get a site key and a secret key, and tell us the hostnames the widget will run on.
  2. Add the widget script and element to your form. The widget adds a hidden field called pol-token.
  3. On your server, send that token and your secret key to POST https://api.heliusnetwork.app/v1/siteverify and accept the request only when success is true.
<!-- in the page -->
<script src="https://cdn.heliusnetwork.app/pol/v1/gate.js" async defer></script>
<form method="post" action="/signup">
  <input name="email" type="email">
  <div class="pol-gate" data-sitekey="pk_your_site_key"></div>
  <button>Create account</button>
</form>

The first time, people complete a 9-second motion check on their phone (three moves, then a screen-lock confirmation). On a computer the widget shows a QR code. After that, their Verified Human Pass is reused for 30 days, usually with one screen-lock confirmation. Proof-of-Life uses no camera and no biometrics.

The two-line CAPTCHA swap

Most sites change two lines in the page, and one URL and one field name on the server.

<!-- before (reCAPTCHA v2) -->
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="..."></div>

<!-- after (PoL Gate) -->
<script src="https://cdn.heliusnetwork.app/pol/v1/gate.js" async defer></script>
<div class="pol-gate" data-sitekey="pk_..."></div>
reCAPTCHAPoL Gate
Form fieldg-recaptcha-responsepol-token
Verify URLgoogle.com/recaptcha/api/siteverifyapi.heliusnetwork.app/v1/siteverify
Requestsecret, response, remoteipThe same
Success fieldsuccesssuccess, plus human, unique, present
Token reuseSingle use, about 2 minutesSingle use, 120 seconds

hCaptcha (class="h-captcha") and Turnstile (class="cf-turnstile") swap the same way. data-callback, data-expired-callback and data-error-callback work as before, and the JavaScript API mirrors grecaptcha: PoL.render(el, opts), PoL.execute(sitekey, { action }), PoL.getResponse(id) and PoL.reset(id).

reCAPTCHA v3 returns a score for how human traffic looks. Proof-of-Life instead reports whether its Liveness Engine accepted a live, hand-held motion check, so replace score >= 0.5 with success === true. Require unique === true only where duplicates cost you money, such as sign-up bonuses, rewards and votes: a brand-new device starts below the default threshold and becomes unique with history or a co-verification.

siteverify: request and response

POST https://api.heliusnetwork.app/v1/siteverify, form-encoded or JSON, from your server only. Never put your secret key in a page or an app.

FieldRequiredMeaning
secretYesYour secret key
responseYesThe token from pol-token or PoL.execute
remoteipNoAccepted for compatibility, not stored
expected_actionNoFail with action-mismatch unless the token's action matches
expected_account_hint_hashNoFail with account-mismatch unless the step-up was started for this account
expected_subjectNoFail with subject-mismatch unless the token's pairwise subject equals the one you stored at sign-up
allow_new_deviceNoRecovery only: accept a recovery by a person other than the one bound to the account, and move the binding to them

Response

{
  "success": true, "human": true, "unique": true, "present": true,
  "level": "gate", "action": "signup", "hostname": "shop.example",
  "challenge_ts": "2026-10-22T09:00:00.000Z", "pass_age_s": 42,
  "subject": "pairwise-id-for-your-site",
  "liveness_confidence": 0.95, "uniqueness_confidence": 0.8,
  "uniqueness_level": "history", "error-codes": []
}
FieldMeaning
successThe token is valid, unused, unexpired, for your site, and every expected_* check passed
human, presentThe Liveness Engine accepted the motion check as a live person holding a phone (passed at 0.7 or more). It is a strong signal, not a guarantee
uniqueuniqueness_confidence is at or above your site's threshold (default 0.6)
levelgate, stepup, recovery, liveness or activity
liveness_confidence0 to 1 from the Liveness Engine at the last full check (passes at 0.7 or more)
uniqueness_confidence0 to 1 from the Uniqueness Engine. A confidence score, not a guarantee
uniqueness_levelStrongest evidence so far: device (passkey-based device anchor only), history (several days or places) or vouched (co-verified)
subjectPseudonymous ID, stable for the same person on your site and different on every other site
account_bindingStep-up and recovery only: first, match or rebound
activityActivity tokens only: { type, km, dur_s, cf }

Every response token is single use. Gate, step-up, recovery and liveness tokens expire after 120 seconds and activity tokens after 10 minutes. A second check of the same token returns timeout-or-duplicate. Tokens are signed, and the public keys are published at https://api.heliusnetwork.app/.well-known/jwks.json for offline verification. Anchoring a hash on Solana is optional.

Step-up with account binding

A step-up shows a live person completed the check for one exact action. After the first trusted step-up binds the account, later step-ups also check it is the same person.

  1. Your server starts the step-up with the action and a hash of your internal user ID. Never send an email address or user ID in clear.
  2. The browser runs the check with the returned client_token.
  3. Your server checks the token with the same action and account hash.
// 1. your server
POST https://api.heliusnetwork.app/v1/stepup/start
{ "secret": "sk_...", "action": "transfer",
  "account_hint_hash": "<base64url HMAC-SHA256 of your user id>", "level": "stepup" }
// returns { "session_id", "client_token", "expires_in" }

// 2. the browser
const token = await PoL.execute('pk_your_site_key', { action: 'transfer', stepup: client_token });

// 3. your server
POST https://api.heliusnetwork.app/v1/siteverify
{ "secret": "sk_...", "response": token, "expected_action": "transfer",
  "expected_account_hint_hash": "<same hash>" }

For the strongest guarantee, store the pairwise subject from the user's sign-up token and pass it as expected_subject on every step-up. Start errors: invalid-action, invalid-account-hint-hash, invalid-level, 403 plan-does-not-include-level and 402 plan-limit-reached. Step-up is included from the Business plan, and recovery on Enterprise.

Activity verification

The Activity Verification API checks a recorded walk, run, ride or wheelchair session. An app calls POST /v1/activity/start with its site key, records GPS points and accelerometer samples, encrypts them into an envelope with the returned key, and sends it to POST /v1/activity/verify. Your server can also call verify directly with your secret key.

// envelope contents
{ "activity": "walking | running | cycling | wheelchair",
  "points": [{ "lat", "lng", "t", "acc" }, ...],
  "accel": [{ "t", "x", "y", "z" }, ...],
  "device": { "emulator": false, "rooted": false, "devMode": false, "mockLocation": false, "vpn": false } }

// response
{ "verified": true, "confidence": 1, "hard": [], "soft": [],
  "summary": { "activity": "running", "distanceKm": 5.1, "durationS": 1800, "points": 181 },
  "token": "..." }
LayerHard failureSoft (lowers confidence)
1 SpeedA segment faster than the activity's limit: walking 9, running 25, cycling 45, wheelchair 20 km/h
2 TeleportMore than 200 km in under 10 minutes
3 Sensor fusionGPS moved but the accelerometer shows a still phone
4 Route entropyBearing entropy under 0.2, a ruler-straight replay
5 Time consistencyDistance impossible for the duration, or timestamps out of order
6 Device integrityEmulator, mock location, rooted, developer mode, VPN, poor GPS accuracy

verified needs no hard failure and a confidence of 0.5 or more. Activity tokens can be checked at siteverify for 10 minutes. Raw GPS and motion data is deleted within 60 seconds of the check.

Error codes

CodeWhereMeaning
missing-input-secretsiteverifyNo secret key was sent
invalid-input-secretsiteverifyThe secret key is wrong or revoked
missing-input-responsesiteverifyNo token was sent
invalid-input-responsesiteverifyThe token is malformed, not signed by Helius or for another site
timeout-or-duplicatesiteverifyThe token expired or was already checked
action-mismatchsiteverifyThe token's action is not expected_action
account-mismatchsiteverify, ceremonyThe step-up or recovery was for another account, or by another person
subject-mismatchsiteverifyThe token's subject is not expected_subject
bad-requestsiteverifyThe request could not be parsed
rate-limitedany route (429)Too many requests; retry with backoff
invalid-sitekeyany route (400)The site key is unknown
hostname-not-allowedany route (403)The page's hostname is not registered for the site key
plan-limit-reachedany route (402)The monthly ceremony allowance is used up
firewall-blockedany route (429)Blocked by the Cognitive Firewall after anomalous traffic

Integration guides

Questions or a pilot? Write to hashim@heliusnetwork.com. Data handling is described in the Proof-of-Life privacy notice.