Skip to main content
Signup is the highest-leverage place to run Tripwire. Every downstream abuse pattern — spam, scraping, ratings manipulation, trial farming — starts with a bot-created account. A hard block here is cheaper than cleaning up later.

The threat

Automated signup is the most common and most costly bot surface. Attackers create accounts in bulk to seed spam networks, farm free-trial credits, inflate referral payouts, reserve usernames, drain rate limits, or build a corpus of “aged” accounts for later sale. Because account creation is only occasional for legitimate users, you can afford a stricter policy here than on, say, a login page that a human might hit dozens of times a week. Tripwire fits the shape of this problem well: you want a strong verdict before the row is written. The browser client streams observations the whole time a user is filling out the form, and getSession() flushes everything into a sealed handoff right at submit. Your backend verifies the token, checks the verdict, and decides whether to proceed — all before you touch the database. Two detection categories matter most on this surface:
  • automation — Playwright, Puppeteer, Selenium, Patchright, and related stealth tooling driving a headless browser.
  • ai-agent — LLM-powered agents (browser-use, computer-use, etc.) doing end-to-end signup through a controlled browser.
Tripwire detects both. On a signup page, it is reasonable to hard-block either.

The flow

1

Start Tripwire on page load

The browser client begins collection immediately so a full fingerprint is available by the time the user submits.
2

Wait for fingerprint readiness before enabling submit

waitForFingerprint() resolves when server-side fingerprinting has frozen. This is the safe moment to allow submission.
3

Request a sealed handoff at submit

getSession() flushes the latest observations and returns { sessionId, sealedToken }.
4

Verify on the backend

Use safeVerifyTripwireToken() with your secret key — local, no extra network call.
5

Apply policy

Hard-block on bot, step up on inconclusive, proceed on human.

Client integration

Start Tripwire early and gate submission on fingerprint readiness. You can show the form the whole time — just disable the submit button until waitForFingerprint() has resolved.
<script type="module">
  const tripwirePromise = import("https://cdn.tripwirejs.com/t.js").then(
    (Tripwire) =>
      Tripwire.start({
        publishableKey: "pk_live_your_publishable_key",
      }),
  );

  const submitButton = document.querySelector("#signup-submit");

  tripwirePromise.then(async (tripwire) => {
    tripwire.onError((error) => {
      console.error("Tripwire error", error.code, error.message);
    });

    await tripwire.waitForFingerprint();
    submitButton.disabled = false;
  });

  async function submitSignup(formData) {
    const tripwire = await tripwirePromise;
    const { sessionId, sealedToken } = await tripwire.getSession();

    return fetch("/api/signup", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        ...formData,
        tripwire: { sessionId, sealedToken },
      }),
    });
  }
</script>
If your signup form is complex enough that waitForFingerprint() might not have resolved by the time a fast user clicks submit, see Checkout & payment for the fallback pattern.

Server verification

const { safeVerifyTripwireToken } = require("@abxy/tripwire-server");

app.post("/api/signup", async (req, res) => {
  const result = safeVerifyTripwireToken(
    req.body.tripwire.sealedToken,
    process.env.TRIPWIRE_SECRET_KEY,
  );

  if (!result.ok) {
    // Fail closed on signup — no token, no account.
    return res.status(403).json({ error: "Verification failed" });
  }

  const { decision, attribution } = result.data;

  if (decision.verdict === "bot") {
    return res.status(403).json({ error: "Signup blocked" });
  }

  if (decision.verdict === "inconclusive") {
    // Hold the signup and require email verification before activating
    await createAccount({ ...req.body, status: "pending_verification" });
    await sendVerificationEmail(req.body.email);
    return res.json({ status: "verify_email" });
  }

  await createAccount(req.body);
  res.json({ status: "created" });
});

Decisioning policy

VerdictRecommended action on signup
humanCreate the account.
inconclusiveCreate the account in a pending_verification state and require email (or phone) confirmation before unlocking it.
botReturn a generic error. Do not create the account.
Two policy choices worth making deliberately:
  • Fail closed when verification fails. On a login page you might fail open (see Login & credential stuffing). On signup, returning an error is safe — a real user can refresh and retry, and you’ve prevented any attacker from bypassing Tripwire by sending a malformed token.
  • Do not leak detection to the client. Return the same message for bot and a generic server error. The browser client never receives verdicts directly — keeping them invisible at the response layer too means attackers get no signal to iterate against.
If you run Going to production’s report-only mode on signup, watch decision.verdict === "bot" and the attribution.bot.facets.category.value field (values: automation, ai-agent, crawler, unknown) to understand what you’d be blocking before you turn enforcement on.

Gate: the opinionated alternative

If you’d rather not build the signup form, email verification, and bot policy yourself, Gate is Tripwire’s passwordless signup flow with the detection stack built in. Your service registers once with a webhook URL, and Gate handles the UI, consent, Tripwire verification, and approver handoff on your behalf. Choose Gate when:
  • You’re building a developer tool or API product where every user needs an account, but you don’t want to own the signup UI.
  • You want account creation to be agent-aware by default — Gate issues short-lived agent tokens alongside human logins.
  • You’d rather receive a signed webhook than run your own form + verification pipeline.
Choose the integration on this page when:
  • You already have a signup form and just want to stop bots from submitting it.
  • You need fine-grained control over the inconclusive → email verification handoff.
  • You’re on a platform (native mobile, embedded, etc.) where Gate’s hosted flow doesn’t fit.
See What is Gate for the full flow.

What’s next

Server verification

Reference for the underlying verification primitive.

Going to production

Rollout plan — report-only, soft challenge, hard enforcement.

Login protection

Protect the login form against credential stuffing.

What is Gate

Opinionated passwordless signup built on Tripwire.