Bot Shield
Invisible proof-of-work bot protection. No CAPTCHAs, no image puzzles, no user friction. Two lines to integrate.
How it works
Every workspace can hand out short-lived challenge tokens. A challenge is a random prefix plus a target difficulty in bits. The client must find a nonce such that sha256(prefix + nonce) has N leading zero bits (default 18 ≈ ~200ms of CPU). Real browsers do this invisibly in a Web Worker. Scripted bots without JS can't solve it; those that can pay real CPU per request — which is what kills bulk abuse.
The proof (botToken) is an HMAC-signed capability bound to your workspace and an expiry. It lives 15 minutes in sessionStorage. Attach it to /v1/evaluate. Done.
Modes
Configure in Settings → Bot Protection.
- off — ignored.
- monitor — a missing/invalid proof adds a no_bot_proof signal to the risk score. Recommended for a week to see impact.
- enforce — a missing proof forces STEP_UP_AUTH and the response returns a fresh challenge so the client can solve and retry.
Option A — drop-in widget (recommended)
<script
src="https://veriloop.lovable.app/veriloop-bot.js"
data-key="vl_live_..."></script>
<!-- Any form gets auto-protected: -->
<form data-veriloop-protect action="/signup" method="post">
<input name="email" />
<button>Sign up</button>
</form>The widget warms a token in the background on page load. When the form submits, the widget injects a hidden veriloop_bot_token field. On your server, forward it to /v1/evaluate as botToken.
Or call the widget directly:
// Explicit API — auto-refreshes on 401
const res = await window.Veriloop.evaluate({
action: "login",
userId: "u_123",
ip,
});
if (res.decision === "BLOCK") location.replace("/blocked");Option B — manual PoW
// 1. Issue
const { challenge } = await fetch("/api/public/v1/challenge/issue", {
method: "POST",
headers: { authorization: `Bearer ${KEY}` },
}).then(r => r.json());
// 2. Solve
async function sha256Hex(s) { /* ...crypto.subtle.digest... */ }
function bits(hex) { /* count leading zero bits */ }
let nonce = 0, hex;
do { hex = await sha256Hex(challenge.prefix + (++nonce)); }
while (bits(hex) < challenge.difficulty);
// 3. Verify
const { botToken } = await fetch("/api/public/v1/challenge/verify", {
method: "POST",
headers: { authorization: `Bearer ${KEY}`, "content-type": "application/json" },
body: JSON.stringify({ challenge, nonce: String(nonce) }),
}).then(r => r.json());
// 4. Attach to evaluate calls
await fetch("/api/public/v1/evaluate", {
method: "POST",
headers: { authorization: `Bearer ${KEY}`, "content-type": "application/json" },
body: JSON.stringify({ action: "login", userId, botToken }),
});What if enforce mode returns a challenge?
In enforce mode, a request without a valid botToken returns decision: STEP_UP_AUTH and a fresh botProtection.challenge. Solve it, retry with the new token, and continue.
Rate limits
Public endpoints are rate-limited per (API key + client IP) at roughly 60 rps burst / 600 rpm sustained for evaluate, and 20 rps for challenge endpoints. Exceeding returns 429 with Retry-After.
Testing
VERILOOP_KEY=vl_live_... VERILOOP_BASE=https://YOUR-APP.lovable.app \
node scripts/test-bot-shield.mjsSignals it feeds
no_bot_proof, headless_ua, and bot_burst are all learned weights. Label events in the dashboard to teach the engine what's real bot traffic vs benign automation.