Skip to main content
Foil is designed around backend verification. The browser sends your backend a fresh { sessionId, sealedToken } handoff, and your backend decides whether to allow, challenge, throttle, or block the action.
1

Start Foil early

Initialize the browser client on page load so collection begins before the user reaches the protected action.
2

Request a fresh session handoff

Right before signup, checkout, login, or another sensitive action, call foil.getSession().
3

Submit the handoff with the business action

Send { sessionId, sealedToken } to your backend alongside your normal payload.
4

Verify on the server

Use the SDK to verify the sealed token locally with your secret key.
5

Apply policy

Allow, challenge, rate-limit, or block based on the verdict.

Browser handoff

<script type="module">
  const { sessionId, sealedToken } = await foil.getSession();

  await fetch("/api/signup", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      ...formData,
      foil: { sessionId, sealedToken },
    }),
  });
</script>

Verify the sealed token

The primary verification path. Local, fast, no network call to Foil.
The method is named safeVerifyFoilToken (and snake/Pascal variants) in every SDK except PHP, where it is SealedToken::safeVerify.
const { safeVerifyFoilToken } = require("@abxy/foil-server");

app.post("/api/signup", (req, res) => {
  const result = safeVerifyFoilToken(
    req.body.foil.sealedToken,
    process.env.FOIL_SECRET_KEY,
  );

  if (!result.ok) {
    return res.status(500).json({ error: "Verification failed" });
  }

  console.log(result.data.decision.verdict);    // "human", "bot", or "inconclusive"
  console.log(result.data.decision.risk_score); // 0–100
  console.log(result.data.session_id);

  if (result.data.decision.verdict === "bot") {
    return res.status(403).json({ error: "Blocked" });
  }

  // Proceed with the action
});

Alternative: durable session readback

If you prefer to fetch the full session from the API (useful for async or audit workflows):
const resp = await fetch(
  `https://api.usefoil.com/v1/sessions/${sessionId}`,
  { headers: { Authorization: `Bearer ${process.env.FOIL_SECRET_KEY}` } }
);
const session = await resp.json();

console.log(session.data.decision.automation_status); // "human", "automated", or "uncertain"
console.log(session.data.decision.risk_score);        // 0 – 100
Key fields in the durable session response:
FieldDescription
decision.automation_statushuman, automated, or uncertain
decision.risk_scoreinteger 0 (human) to 100 (bot)
decision.evaluation_phasesnapshot, behavioral, or null
decision.decision_statuspreliminary or final
client_user_idYour own end-user ID, if you attached one
highlightsTop signals that influenced the decision
automationDetected automation framework details
visitor_fingerprintDurable device identifier

Attach your user ID

If the Foil session starts before the user exists in your database, attach your user ID after signup succeeds from your backend:
await foil.sessions.attachClientUser(sessionId, user.id);
// Later, if needed:
await foil.sessions.clearClientUser(sessionId);

Policy patterns

PatternWhen to useExample
Report onlyFirst week of integrationLog verdicts, don’t block anyone
Soft challengeinconclusive on sensitive endpointsShow CAPTCHA or require email verification
Hard blockbot on signup, checkout, scrapingReturn 403 immediately
Rate controlRepeated fingerprintsSlow down instead of blocking
Start in report-only mode. Once you understand your traffic’s verdict distribution, gradually enable enforcement.

What’s next