> ## Documentation Index
> Fetch the complete documentation index at: https://usefoil.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Server verification

> Verify Foil sealed tokens on your backend with a local check, no network call, then read the verdict and risk score to allow, challenge, or block actions.

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.

## Recommended flow

<Steps>
  <Step title="Start Foil early">
    Initialize the browser client on page load so collection begins before the user reaches the protected action.
  </Step>

  <Step title="Request a fresh session handoff">
    Right before signup, checkout, login, or another sensitive action, call `foil.getSession()`.
  </Step>

  <Step title="Submit the handoff with the business action">
    Send `{ sessionId, sealedToken }` to your backend alongside your normal payload.
  </Step>

  <Step title="Verify on the server">
    Use the SDK to verify the sealed token locally with your secret key.
  </Step>

  <Step title="Apply policy">
    Allow, challenge, rate-limit, or block based on the verdict.
  </Step>
</Steps>

## Browser handoff

```html theme={"dark"}
<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.

<Note>
  The method is named `safeVerifyFoilToken` (and snake/Pascal variants) in every SDK except PHP, where it is `SealedToken::safeVerify`.
</Note>

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  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
  });
  ```

  ```python Python theme={"dark"}
  from foil_server import safe_verify_foil_token
  import os

  @app.post("/api/signup")
  def signup(request):
      result = safe_verify_foil_token(
          request.json["foil"]["sealedToken"],
          os.environ["FOIL_SECRET_KEY"],
      )

      if not result.ok:
          return {"error": "Verification failed"}, 500

      print(result.data.decision.verdict)    # "human", "bot", or "inconclusive"
      print(result.data.decision.risk_score) # 0 – 100

      if result.data.decision.verdict == "bot":
          return {"error": "Blocked"}, 403

      # Proceed with the action
  ```

  ```go Go theme={"dark"}
  import foil "github.com/abxy-labs/foil-server-go"

  func signupHandler(w http.ResponseWriter, r *http.Request) {
      var body struct {
          Foil struct {
              SealedToken string `json:"sealedToken"`
          } `json:"foil"`
      }
      json.NewDecoder(r.Body).Decode(&body)

      result := foil.SafeVerifyFoilToken(
          body.Foil.SealedToken,
          os.Getenv("FOIL_SECRET_KEY"),
      )
      if !result.OK {
          http.Error(w, "Verification failed", 500)
          return
      }

      if result.Data.Decision.Verdict == "bot" {
          http.Error(w, "Blocked", 403)
          return
      }

      // Proceed with the action
  }
  ```

  ```ruby Ruby theme={"dark"}
  require "foil/server"

  post "/api/signup" do
    body = JSON.parse(request.body.read)
    result = Foil::Server::SealedToken.safe_verify_foil_token(
      body.dig("foil", "sealedToken"),
      ENV.fetch("FOIL_SECRET_KEY"),
    )

    halt 500, { error: "Verification failed" }.to_json unless result[:ok]

    decision = result[:data][:decision]
    halt 403, { error: "Blocked" }.to_json if decision[:verdict] == "bot"

    # Proceed with the action
  end
  ```

  ```php PHP theme={"dark"}
  use Foil\Server\SealedToken;

  $body = json_decode(file_get_contents("php://input"), true);
  $result = SealedToken::safeVerify(
      $body["foil"]["sealedToken"],
      getenv("FOIL_SECRET_KEY"),
  );

  if (!$result->ok) {
      http_response_code(500);
      echo json_encode(["error" => "Verification failed"]);
      exit;
  }

  if ($result->data->decision["verdict"] === "bot") {
      http_response_code(403);
      echo json_encode(["error" => "Blocked"]);
      exit;
  }

  // Proceed with the action
  ```

  ```bash cURL theme={"dark"}
  # Use the SDK for local verification. For API-based readback, see below.
  ```
</CodeGroup>

## Alternative: durable session readback

If you prefer to fetch the full session from the API (useful for async or audit workflows):

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  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
  ```

  ```python Python theme={"dark"}
  import requests

  resp = requests.get(
      f"https://api.usefoil.com/v1/sessions/{session_id}",
      headers={"Authorization": f"Bearer {os.environ['FOIL_SECRET_KEY']}"},
  )
  session = resp.json()

  print(session["data"]["decision"]["automation_status"])
  print(session["data"]["decision"]["risk_score"])
  ```

  ```bash cURL theme={"dark"}
  curl https://api.usefoil.com/v1/sessions/sid_... \
    -H "Authorization: Bearer sk_live_..."
  ```
</CodeGroup>

Key fields in the durable session response:

| Field                        | Description                               |
| ---------------------------- | ----------------------------------------- |
| `decision.automation_status` | `human`, `automated`, or `uncertain`      |
| `decision.risk_score`        | integer `0` (human) to `100` (bot)        |
| `decision.evaluation_phase`  | `snapshot`, `behavioral`, or `null`       |
| `decision.decision_status`   | `preliminary` or `final`                  |
| `client_user_id`             | Your own end-user ID, if you attached one |
| `highlights`                 | Top signals that influenced the decision  |
| `automation`                 | Detected automation framework details     |
| `visitor_fingerprint`        | Durable 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:

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  await foil.sessions.attachClientUser(sessionId, user.id);
  // Later, if needed:
  await foil.sessions.clearClientUser(sessionId);
  ```

  ```python Python theme={"dark"}
  client.sessions.attach_client_user(session_id, user.id)
  # Later, if needed:
  client.sessions.clear_client_user(session_id)
  ```

  ```go Go theme={"dark"}
  _, err := client.Sessions.AttachClientUser(ctx, sessionID, user.ID)
  _, err = client.Sessions.ClearClientUser(ctx, sessionID)
  ```

  ```ruby Ruby theme={"dark"}
  client.sessions.attach_client_user(session_id, user.id)
  client.sessions.clear_client_user(session_id)
  ```

  ```php PHP theme={"dark"}
  $client->sessions()->attachClientUser($sessionId, $user->id);
  $client->sessions()->clearClientUser($sessionId);
  ```

  ```bash cURL theme={"dark"}
  curl -X PATCH https://api.usefoil.com/v1/sessions/sid_... \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{"client_user_id":"user_123"}'
  ```
</CodeGroup>

## Policy patterns

| Pattern            | When to use                           | Example                                    |
| ------------------ | ------------------------------------- | ------------------------------------------ |
| **Report only**    | First week of integration             | Log verdicts, don't block anyone           |
| **Soft challenge** | `inconclusive` on sensitive endpoints | Show CAPTCHA or require email verification |
| **Hard block**     | `bot` on signup, checkout, scraping   | Return 403 immediately                     |
| **Rate control**   | Repeated fingerprints                 | Slow down instead of blocking              |

<Tip>
  Start in report-only mode. Once you understand your traffic's verdict distribution, gradually enable enforcement.
</Tip>

## What's next

* [Testing your integration](/testing) — simulate bot traffic and debug
* [Going to production](/going-to-production) — rollout checklist
* [Verdicts & scoring](/verdicts-and-scoring) — understand what scores mean
* [Sessions API](/api-reference/sessions) — full API reference
