One endpoint. Behavioral signals. A confidence score. No friction for your users — and no false positives.
The snippet silently collects behavioral signals (mouse, keyboard, scroll) and sends them with each verify call for a richer score.
All requests must include your API key in the Authorization header as a Bearer token.
Get your key from your dashboard →
/api/v1/verifyVerify a userAnalyzes a user's behavioral signals and returns a human confidence score. Call at critical moments — signups, logins, form submissions, high-value actions.
| Parameter | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Your internal user identifier. Not stored — used only for behavioral analysis. |
| Field | Type | Description |
|---|---|---|
| human | boolean | True if the user is likely human (confidence ≥ 0.5). |
| confidence | number | Score from 0.0 to 1.0. Higher = more human-like behavior. |
| token | string | Unique verification token. Store for audit purposes. |
| pii_stored | boolean | Always false. useHUMA never stores personal information. |
| agent | object | Pro & Enterprise. AI-agent detection: { is_agent, agent_confidence (0–1), signals[] }. Parallel to the human score — present only when behavioral signals are sent. Omitted on lower plans. |
curl -X POST https://humaverify.com/api/v1/verify \
-H "Authorization: Bearer huma_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"userId": "user_abc123"}'{
"human": true,
"confidence": 0.92,
"token": "h_verified_Kd3mP8luYMC2Mi1x",
"pii_stored": false,
"agent": { // Pro & Enterprise
"is_agent": false,
"agent_confidence": 0,
"signals": []
}
}On Pro & Enterprise, every verification returns an agent block alongside the human score. It flags AI agents driving a real browser — Playwright, Puppeteer, Selenium, and extension / computer-use agents — even when the session looks human. It only appears when behavioral signals are sent.
One response, a three-way decision: check human first, then agent.is_agent. The action is always yours.
const { human, confidence, agent } = await res.json();
if (!human) {
// Not human — block or challenge (bot / automated abuse)
return reject();
}
if (agent?.is_agent) {
// A real browser driven by an AI agent (Pro & Enterprise).
// Your call: allow the automation you want, or block / step-up the rest.
return challenge();
}
// Trusted human — let them through
return allow();Detection is high-confidence, not absolute — a perfectly throttled agent that mimics human input is an arms race. Use agent_confidence to tune how aggressively you act.
/api/v1/sessionHeartbeat — continuous monitoringMonitor an active user session for bot behavior changes and account takeovers. Call every 30 seconds after a successful verify(). Returns a live score and flags anomalies when behavior drops significantly from the user's verified baseline.
| Parameter | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Same userId used in verify(). |
| sessionToken | string | Yes | The token returned by verify() — used to fetch the behavioral baseline. |
| sessionData | object | No | Behavioral signals from the current window (same shape as verify sessionData). |
| Field | Type | Description |
|---|---|---|
| score | number 0–100 | Live session score for this window. |
| confidence | number 0–1 | Confidence as a decimal. |
| baseline | number 0–100 | Score from the original verify — used as comparison reference. |
| delta | number | Score drop from baseline. Negative = degraded behavior. |
| anomaly | boolean | True when delta > 28 points or score < 30 (absolute bot threshold). |
| action | string | allow | flag | block |
| notes | string[] | Signal labels explaining the score (e.g. organic_mouse, bot_typing). |
// After a successful verify():
const result = await Huma.verify('user_123', 'huma_live_...');
if (result.human) {
Huma.startSession('user_123', 'huma_live_...', result.token, {
intervalMs: 30000, // every 30 seconds
onAnomaly: function(r) {
if (r.action === 'block') {
window.location.href = '/logout'; // account takeover detected
}
}
});
}import { useHumaSession } from 'usehuma/react';
const { anomaly, lastResult } = useHumaSession({
apiKey: 'huma_live_...',
userId: 'user_123',
token: verifyResult.token,
onAnomaly: (r) => router.push('/logout'),
});Receive real-time HTTP POST notifications when a bot is detected or a human is verified. Signatures use HMAC-SHA256 — the same pattern as Stripe. Max 5 webhooks per API key.
/api/v1/webhooksList your webhooks/api/v1/webhooksRegister a webhook| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Your HTTPS endpoint. Must start with https:// |
| events | string[] | No | Events to receive: bot.detected, human.verified. Defaults to both. |
⚑ The secret is shown only once. Save it immediately.
{
"id": "wh_abc123...",
"secret": "huma_whsec_abc123...", // shown once
"url": "https://yourdomain.com/webhooks/huma",
"events": ["bot.detected", "human.verified"],
"active": true
}Each webhook includes a Huma-Signature header. Verify it to confirm the request came from useHUMA.
Huma-Signature: t=1716840000,v1=abc123def456...
import crypto from 'crypto';
function verifyWebhook(rawBody, signature, secret) {
const [tPart, vPart] = signature.split(',');
const timestamp = tPart.replace('t=', '');
const received = vPart.replace('v1=', '');
const payload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}{
"event": "bot.detected",
"user_id": "user_123",
"human": false,
"confidence": 0.18,
"score": 18,
"token": "h_verified_Kd3mP8luYMC2Mi1x",
"timestamp": "2026-05-25T21:00:00.000Z"
}When confidence is borderline (30–70%), present a visual challenge instead of hard-blocking. If passed, the user receives a verification token equivalent to a standard verify. Max 3 attempts per challenge. Challenges expire in 5 minutes.
/api/v1/challenge/createGenerate a challenge| Parameter | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Your internal user identifier. |
{
"challenge_id": "ch_abc123...",
"type": "click_targets", // or "pattern"
"payload": {
"instruction": "Click the 🎯",
"targets": [
{ "id": "t0", "x": 20, "y": 25, "label": "🌿" },
{ "id": "t1", "x": 65, "y": 25, "label": "🎯" },
...
]
},
"expires_in_seconds": 300
}/api/v1/challenge/verifySubmit the user's answer| Parameter | Type | Required | Description |
|---|---|---|---|
| challenge_id | string | Yes | ID from challenge/create. |
| answer | string | Yes | For click_targets: target id (e.g. "t1"). For pattern: dot sequence (e.g. "1-5-9"). |
{ "passed": true, "token": "h_challenge_abc..." }{ "passed": false, "message": "Incorrect answer", "attempts_remaining": 2 }import { HumaChallenge } from 'usehuma/react';
// After verify() returns borderline confidence:
const ch = await fetch('/api/v1/challenge/create', {
method: 'POST',
headers: { Authorization: 'Bearer huma_live_...' },
body: JSON.stringify({ userId }),
}).then(r => r.json());
return (
<HumaChallenge
challengeId={ch.challenge_id}
type={ch.type}
payload={ch.payload}
apiKey="huma_live_..."
onPassed={(token) => continueLogin(token)}
onBlocked={() => router.push('/blocked')}
/>
);| Status | Code | Meaning |
|---|---|---|
| 200 | — | Verification successful. |
| 401 | MISSING_AUTH | No Authorization header provided. |
| 401 | INVALID_API_KEY_FORMAT | Key doesn't match format (huma_live_...). |
| 401 | INVALID_API_KEY | Key not found in database. Check your dashboard. |
| 400 | — | Missing or invalid userId in request body. |
| 402 | TRIAL_EXPIRED | Free trial has expired. Upgrade at humaverify.com/#pricing. |
| 429 | MONTHLY_LIMIT_EXCEEDED | Monthly plan limit reached. Upgrade your plan. |
| 429 | BURST_LIMIT_EXCEEDED | Too many requests per minute. Check Retry-After header. |
| 429 | USER_FLOOD_DETECTED | Same userId verified too many times per minute (max 10/min). |
| 503 | — | Service temporarily unavailable. Retry with exponential backoff. |
The usehuma package ships CJS + ESM + TypeScript definitions and works in any Node.js, Next.js, or edge runtime. It also exports React hooks and the HumaGate component.
import { verify } from "usehuma";
// Collects behavioral signals from the DOM and verifies in one call.
// Call this client-side at a critical moment (login, signup, checkout).
const result = await verify({
apiKey: "huma_live_...",
userId: "user_123",
});
if (result.human && result.confidence > 0.7) {
// ✅ Real human — send result.token to your server for audit
} else {
// 🚫 Bot — block or trigger step-up challenge
}import { verifyWithSignals } from "usehuma";
// Browser: collect signals and forward them.
// const signals = Huma.debug()?.features;
// fetch('/api/check', { body: JSON.stringify({ userId, signals }) });
// In your API route (server-side):
const result = await verifyWithSignals({
apiKey: process.env.HUMA_API_KEY!, // huma_live_... (keep secret)
userId: req.body.userId,
signals: req.body.signals, // forwarded from browser
});
if (result.human) { /* allow */ }import { useHuma } from "usehuma/react";
function SignupForm() {
const { verify, collecting } = useHuma({
apiKey: "huma_live_...",
userId: currentUser.id,
});
async function handleSubmit() {
const result = await verify();
if (result.human) {
// proceed
}
}
return <button onClick={handleSubmit}>Sign up</button>;
}import { HumaGate } from "usehuma/react";
// Wraps any form — silently verifies on submit.
// Shows HumaChallenge automatically if confidence is uncertain.
<HumaGate
apiKey="huma_live_..."
userId={user.id}
onPass={(result) => submitForm(result.token)}
onBlock={() => showBotError()}
>
<MyForm />
</HumaGate>import { useHumaSession } from "usehuma/react";
// Call after successful verify() to keep watching mid-session.
useHumaSession({
apiKey: "huma_live_...",
userId: user.id,
interval: 45_000, // heartbeat every 45s
onAnomaly: (action) => {
if (action === "block") logout(); // account takeover detected
if (action === "flag") reVerify();
},
});Verify on the server, where your secret key stays secret. Works in Server Actions, Route Handlers and any Node/Edge runtime — no dependency on Next.js itself.
// Bot-check a form inside a Server Action
"use server";
import { humaFormCheck } from "usehuma/next";
export async function signUp(formData: FormData) {
const check = await humaFormCheck(formData); // uses HUMA_API_KEY env
if (!check.human) redirect("/signup?error=bot_detected");
// ...create the account
}// Protect a Route Handler
import { withHumaProtection } from "usehuma/next";
export const POST = withHumaProtection(async (req, huma) => {
const body = await req.json(); // handler still gets the body
return Response.json({ ok: true, confidence: huma.confidence });
});Submissions with no behavioral signals (scripted direct POSTs) are rejected outright; API outages fail open so real users are never blocked. Full walkthrough: Next.js guide →
import type { VerifyResult, SessionResult } from "usehuma";
// VerifyResult
// { human: boolean; confidence: number; score: number;
// token: string; pii_stored: false }
// SessionResult
// { score: number; delta: number; anomaly: boolean;
// action: "allow" | "flag" | "block"; notes: string[] }Get your API key in seconds. 14-day free trial. No credit card required.
GET YOUR API KEY →