Integration recipes
Copy-paste, working examples. Replace YOUR-APP.lovable.app with your project host.
Express middleware
Score every incoming request. Block on BLOCK, redirect to MFA on STEP_UP_AUTH.
server.js
import express from "express";
const app = express();
const VERILOOP_URL = "https://YOUR-APP.lovable.app/api/public/v1/evaluate";
app.use(async (req, res, next) => {
try {
const r = await fetch(VERILOOP_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VERILOOP_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: req.user?.id,
ip: req.ip,
deviceId: req.headers["x-device-id"],
action: "api_call",
userAgent: req.headers["user-agent"],
}),
}).then((r) => r.json());
res.setHeader("x-veriloop-event", r.eventId ?? "");
if (r.decision === "BLOCK") return res.status(403).json({ error: "blocked" });
if (r.decision === "STEP_UP_AUTH") return res.redirect("/mfa");
} catch (err) {
// fail open — never break the user's traffic on a Veriloop outage
console.error("veriloop", err);
}
next();
});Next.js middleware (Edge)
middleware.ts
import { NextResponse, type NextRequest } from "next/server";
export async function middleware(req: NextRequest) {
const r = await fetch(
"https://YOUR-APP.lovable.app/api/public/v1/evaluate",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VERILOOP_KEY!}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: req.cookies.get("uid")?.value,
ip: req.ip,
deviceId: req.cookies.get("did")?.value,
action: "api_call",
userAgent: req.headers.get("user-agent") ?? undefined,
}),
},
).then((r) => r.json());
if (r.decision === "BLOCK") return new NextResponse("Blocked", { status: 403 });
return NextResponse.next();
}
export const config = { matcher: ["/api/:path*"] };Generic API gateway (Cloudflare Worker)
worker.ts
export default {
async fetch(req: Request, env: { VERILOOP_KEY: string }): Promise<Response> {
const r = await fetch(
"https://YOUR-APP.lovable.app/api/public/v1/evaluate",
{
method: "POST",
headers: {
Authorization: `Bearer ${env.VERILOOP_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
ip: req.headers.get("cf-connecting-ip") ?? undefined,
deviceId: req.headers.get("x-device-id") ?? undefined,
action: "api_call",
userAgent: req.headers.get("user-agent") ?? undefined,
}),
},
).then((r) => r.json());
if (r.decision === "BLOCK") return new Response("Blocked", { status: 403 });
return fetch(req);
},
};Browser HTML — minimal test page
Drop this file anywhere, replace the host and key, then open it in a browser. Useful for sanity-checking the gateway end-to-end.
This puts a key in browser code — only use it for testing from a private machine. For production, call Veriloop from your server.
test.html
<!doctype html>
<html>
<body>
<h1>Veriloop test</h1>
<button id="send">Send test event</button>
<pre id="out"></pre>
<script>
const URL = "https://YOUR-APP.lovable.app/api/public/v1/evaluate";
const KEY = "vl_live_REPLACE_ME";
document.getElementById("send").onclick = async () => {
const out = document.getElementById("out");
out.textContent = "Sending…";
try {
const r = await fetch(URL, {
method: "POST",
headers: {
"Authorization": "Bearer " + KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: "u_demo",
ip: "203.0.113.99",
deviceId: "d_browser",
action: "login",
}),
});
const data = await r.json();
out.textContent = JSON.stringify(data, null, 2);
} catch (err) {
out.textContent = "ERROR: " + err.message;
}
};
</script>
</body>
</html>If the browser test fails
Check, in this order:
- Your URL is exactly
/api/public/v1/evaluate— not/api/v1/evaluate. - Your
Authorizationheader starts withBearer(note the space). - Your key starts with
vl_live_and is not revoked. - Open the browser devtools Network tab — Veriloop always returns JSON, including for errors.
- The
/api/public/v1/healthendpoint should return{ status: "ok" }from anywhere.