01Start here
Choose the action you want to protect, such as submitting a form or creating an account. Run verification immediately before that action.
- Create a site. Add its domain under Your sites. Save the site API key and signing secret when the site is created; the console displays them once.
- Call the browser client. Pass the public sitekey to
runChallenge()and send its token with the action request. - Enforce on your backend. Check the token signature, expiry, sitekey and
decision === 'accept'before processing the action.
A browser check is a risk decision. Keep your normal login, authorization, rate limiting and abuse controls in place.
02Browser integration
Only the sitekey is public. Use this in an ES module (such as a <script type="module"> block or your bundler) when a visitor submits a protected action. Replace the example endpoint with your own backend route.
import { runChallenge } from 'https://instantblock.eu/client/api.js';
async function submitProtectedAction(actionData) {
const result = await runChallenge({
sitekey: 'YOUR_SITEKEY'
});
if (!result.ok || !result.token) {
throw new Error('Verification could not complete. Please retry.');
}
const response = await fetch('/api/protected-action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: result.token, actionData })
});
if (!response.ok) throw new Error('Action could not be completed');
return response.json();
}Show a retry option when verification cannot complete. result.ok means the request completed; it is not the visitor's verdict. A signed token may contain a rejected or unfinished challenge decision, so the presence of result.token is not permission to continue.
03Verify on your server
InstantBlock signs site tokens with HMAC-SHA256 using your site's hex-encoded signing secret. Set IB_SITEKEY and IB_SITE_SECRET as server environment variables. This Node.js example rejects malformed, expired and non-accepted tokens.
const crypto = require('node:crypto');
const express = require('express');
// Add the route below to your existing Express app.
function acceptedToken(token, sitekey, secretHex) {
if (typeof token !== 'string' || !sitekey ||
!/^[0-9a-f]{64}$/i.test(secretHex || '')) return null;
const parts = token.split('.');
if (parts.length !== 3 || parts.some(p => !/^[A-Za-z0-9_-]+$/.test(p))) return null;
const [header, payload, signature] = parts;
const expected = crypto.createHmac('sha256', Buffer.from(secretHex, 'hex'))
.update(header + '.' + payload).digest();
const received = Buffer.from(signature, 'base64url');
if (received.length !== expected.length ||
!crypto.timingSafeEqual(received, expected)) return null;
try {
const meta = JSON.parse(Buffer.from(header, 'base64url').toString('utf8'));
const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
const now = Math.floor(Date.now() / 1000);
if (meta.alg !== 'HS256' || meta.typ !== 'IB-JWT' || meta.kid !== sitekey) return null;
if (!Number.isSafeInteger(claims.iat) || !Number.isSafeInteger(claims.exp)) return null;
if (claims.iat > now + 60 || claims.exp <= now) return null;
if (claims.sitekey !== sitekey || claims.decision !== 'accept' || claims.pz) return null;
return claims;
} catch { return null; }
}
app.post('/api/protected-action', express.json(), (req, res) => {
const claims = acceptedToken(
req.body?.token, process.env.IB_SITEKEY, process.env.IB_SITE_SECRET
);
if (!claims) return res.sendStatus(403);
// Apply your own authorization and perform the protected action here.
return res.json({ ok: true });
});Check the decision. A rejected session can still receive a valid signed token. Checking only the signature would let that request through. The example also rejects the rejection marker pz.
The token is valid for up to one hour. If you need one-time use for a sensitive action, track its jti on your server and reject a second use. Offline signature verification alone does not prevent reuse during the token's lifetime.
04What the decisions mean
The browser check passed. Continue only after your own authorization and business checks.
The client may ask the visitor to complete an interactive step. A successful check upgrades the token. If it does not pass, the original decision remains unaccepted.
Do not complete the action. A reject token may still be signed, so always read its decision.
05Session telemetry
By default, runChallenge() starts a bounded behavior recorder on the protected page. It continues after verification until the page closes, you stop it, or its 12-hour credential expires. Starting another verification finishes the previous recorder and starts a new one.
The recorder captures normalized pointer positions, click and key timing, scroll activity, focus and visibility changes, input-method categories, and resource and performance timings. It does not collect typed characters, form values, clipboard contents, DOM selectors, page URLs or browsing history. Password fields, payment autocomplete fields and elements inside data-ib-private are excluded.
Nonempty batches are encrypted before POST /tm, normally every 15 seconds and when the page becomes hidden or closes. Telemetry files are encrypted at rest and grouped by a site-specific keyed IP hash. Uploads and retries run separately from the verification result; these extra records currently support diagnostics and future model development, without changing the visitor's score.
import { runChallenge, flushTelemetry, stopTelemetry }
from 'https://instantblock.eu/client/api.js';
await runChallenge({ sitekey: 'YOUR_SITEKEY' });
await flushTelemetry(); // Optional: upload the current nonempty batch.
await stopTelemetry(); // Flush once and remove the recorder's listeners.
// Disable the additional recorder for this verification:
await runChallenge({ sitekey: 'YOUR_SITEKEY', telemetry: false });Describe the recorder in your site's privacy notice and apply your site's consent requirements before starting it. Network failures or page closure can prevent delivery; these records are observations, not proof that a visitor is human or automated. The existing 90-day verification-data cleanup also covers telemetry.
06Which credential goes where?
| Credential | Use | Location |
|---|---|---|
sitekey | Identifies the site for browser checks | Browser and server |
secret | Verifies signed decision tokens | Server only |
api_key | Reads your site's API records and configuration | Server only |
decode_seed | Decodes scattered fingerprint records | Server only |
You have no site yet. Create a site to get a sitekey.
The console masks secret values after site creation. If you still have the site's API key, your backend can call GET https://instantblock.eu/api/v1/me with Authorization: Bearer <site API key> to retrieve its signing secret. Never call that endpoint from browser code. The separate keys under Dashboard → API keys are for the console API, not the engine Site API.
07Site API
These endpoints require the site API key in an Authorization: Bearer header. They return records for that site only. Keep the key in a trusted backend or command-line environment.
| Endpoint | Returns |
|---|---|
GET /api/v1/me | Site configuration, signing secret and decode seed |
GET /api/v1/stats | Decision totals; optional since Unix milliseconds |
GET /api/v1/verifications | Recent records; optional limit, since, decision and user_id |
GET /api/v1/verifications/:cid | One record; add ?format=plain for readable fingerprint fields |
GET /api/v1/users/:user_id | Recent sessions for a user identifier you supplied |
GET /api/v1/sessions/:token | Current in-memory status for a signed token or its jti, while available |
curl -H "Authorization: Bearer $IB_SITE_API_KEY" \
"https://instantblock.eu/api/v1/verifications?limit=20"The /admin/* engine routes use a separate master key and are for the service operator. A site API key does not grant access to them.
08Troubleshooting
- My browser call returns no token.
- Check that the site is enabled, the sitekey is correct, and the client can reach the engine. Offer a retry when a network or device check fails.
- The token has a valid signature but access is denied.
- Decode the signed payload on your backend and inspect
decision,sitekey,expandpz. Rejected decisions are intentionally signed. - Why is the API returning 401?
- Use the per-site
api_keywithAuthorization: Bearer. Console account API keys and the engine master key are different credentials. - Where can I review traffic?
- Use Events in the console or the site API. Record IDs let you inspect individual checks.
For help with an integration, contact the InstantBlock team. Include a sitekey and record ID, but never send a secret or API key.