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 Authorization header starts with Bearer  (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/health endpoint should return { status: "ok" } from anywhere.