ShieldWall

Developer docs

Public API reference for Botproof / ShieldWall. Create a site in the console, embed the widget, redeem tokens with /siteverify. Sample keys below are placeholders (sw_site_live_XXXX / sw_secret_live_XXXX).

Quickstart

  1. Create an org + site in the console; copy the public site key and secret (secret shown once).
  2. Embed ShieldWallWidget with your site key and action.
  3. On your backend, redeem responseToken via POST /api/v1/siteverify.

Widget (React)

The widget shows an ALTCHA-style checkbox while it solves PoW and collects signals. On PASS you receive a short-lived responseToken for server redeem.

import { useState } from "react";
import { ShieldWallWidget } from "@shieldwall/client";

export function LoginForm() {
  const [token, setToken] = useState<string | null>(null);

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await fetch("/api/login", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            /* credentials… */
            shieldwallToken: token,
          }),
        });
      }}
    >
      <ShieldWallWidget
        apiBase="https://api.botproof.app/api/captcha"
        siteKey="sw_site_live_XXXX"
        action="login"
        onVerified={setToken}
      />
      <button type="submit" disabled={!token}>Sign in</button>
    </form>
  );
}

POST /api/captcha/challenge

Issues an HMAC-signed nonce + difficulty. Prefer the SDK (it calls this for you). When tenancy is enabled, unknown or disabled site keys return SITE_KEY_INVALID.

curl -sS -X POST 'https://api.botproof.app/api/captcha/challenge' \
  -H 'Content-Type: application/json' \
  -d '{
    "action": "login",
    "siteKey": "sw_site_live_XXXX"
  }'

POST /api/captcha/verify

Browser posts PoW solution + behavioral / fingerprint signals. Prefer ShieldWallWidget — it builds the payload. Response includes verdict, score, optional responseToken (when siteKey was bound), and SW-901 reasonCodes.

// Shape (SDK builds this — do not hand-roll in production)
POST https://api.botproof.app/api/captcha/verify
{
  "nonce": "…",
  "timestamp": 0,
  "difficulty": 3,
  "seed": 0,
  "signature": "…",
  "powNonce": 0,
  "powSolveTimeMs": 120,
  "siteKey": "sw_site_live_XXXX",
  "action": "login",
  "mode": "probabilistic",
  "algo": "sha256-chain"
  /* + behavior / fingerprint fields from the SDK */
}

POST /api/v1/siteverify

Server-to-server redeem (reCAPTCHA-compatible). Never trust the browser alone. Tokens are single-use (~2 min TTL).

curl -sS -X POST 'https://api.botproof.app/api/v1/siteverify' \
  -H 'Content-Type: application/json' \
  -d '{
    "secret": "sw_secret_live_XXXX",
    "response": "<responseToken from /verify>"
  }'
// Example success body
{
  "success": true,
  "verdict": "PASSED",
  "score": 0.92,
  "action": "login",
  "hostname": "example.com",
  "challenge_ts": "2026-07-25T00:00:00.0000000Z",
  "error-codes": null,
  "reason-codes": []
}

Client IP & proxies

Call /challenge and /verify from the browser, directly against https://api.botproof.app. Only the public site key travels to the page; your secret key stays on your server and is used solely by /siteverify. Tell us your site's origin when you create it so it is added to the CORS allowlist.

If you instead proxy those two calls through your own backend, every visitor reaches us as your server's outbound address. IP reputation, velocity, ASN signals and the geographic reports in Sentinel then key off your datacenter rather than the user — and a shared datacenter address is itself a bot signal, so legitimate traffic starts drawing CHALLENGE_REQUIRED.

Verdicts

Reason codes (SW-901)

Additive stable codes on verify (reasonCodes) and siteverify (reason-codes). Legacy siteverify error-codes stay reCAPTCHA-compatible. Enumerated in OpenAPI as components.schemas.ReasonCode. Treat unknown codes as informational.

Full catalog: docs/REASON-CODES.md and docs/openapi/public-api.yamlReasonCode.

OpenAPI

Machine-readable public contract (challenge, verify, siteverify only — no ops/admin): docs/openapi/public-api.yaml. Language samples on this page are built from publicApiContract.ts, which mirrors the YAML example bodies — change both together when the public request shape changes.

Widget copy (product-facing)

Idle: “I'm not a robot” · Verifying: “Verifying… N%” · Success: “Verified” · Escalation: “Extra check required”. Brand footer: SHIELDWALL / Privacy protected.

Interactive states: Playground. Changelog: /changelog.

Protected by ShieldWall
Verifying…