TILLGATE · OVERVIEW

Prove there’s a human.

GA

TillGate is the human check inside TillShield. A widget issues a signed pass token after the visitor’s browser completes a client-side proof-of-work; your server verifies that token at /siteverify. Privacy-first — no third-party tracking, no cross-site cookies — and honest about what it is: a cost on automation plus hostname and replay binding, not a behavioral bot detector.

01Mental model

Three moving parts

  1. Proof-of-work. The widget asks the browser to find a nonce whose hash has a configured number of leading zero bits. Finding it costs CPU; the harder the difficulty, the more it costs — cheaply for one visitor, expensively at bot scale.
  2. A signed pass token. On success the widget writes a short-lived, signed token into a form field named tillgate-response and calls your data-callback. The token is bound to your hostname and carries a short TTL.
  3. Server-side verification. Your backend sends the token and your secret key to /siteverify and gets back success, the hostname, and a challenge timestamp.

That is the entire mechanism. TillGate raises the cost of mass automation and binds each pass to the page that issued it. It does not fingerprint visitors or run a model — position it as friction on cheap automation, not a silver bullet.

02Keys

Concepts & keys

Each project gets a key pair, minted under Shield → TillGate:

  • tg_site_… — the site key. Public, embedded in your page markup. It only identifies which project a pass belongs to; safe to ship to the browser.
  • tg_secret_… — the secret key. A server credential used only in your /siteverify call.
Server only
Never put the tg_secret_… key in client code, a bundle, or a repository. It belongs only in your backend’s /siteverify call.
03Quick start

Hosted script

The fastest path. Add the hosted widget script, then drop a tillgate element with your site key inside the form you want to protect. The widget renders itself, runs the proof-of-work, and puts the pass token in a hidden tillgate-response field that submits with the form.

html
<script src="https://tilldev.dev/tillgate.js" async defer></script>

<form method="POST" action="/login">
  <div class="tillgate" data-sitekey="tg_site_…"></div>
  <button type="submit">Sign in</button>
</form>

Attributes configure the widget: data-sitekey (required), data-mode (managed / invisible / interactive), and data-callback (a global function name called with the token).

04Quick start

npm package

For single-page apps, install the package and render the widget into an element you control. The callback receives the pass token directly.

bash
npm i @tilldev/tillgate
gate.ts
import { render } from '@tilldev/tillgate'

render('#gate', {
  sitekey: 'tg_site_…',
  mode: 'managed',                 // managed | invisible | interactive
  callback: (token) => {
    // send { response: token } to your backend to verify
  },
})

Whichever path you use, the client half only produces a token — it never proves anything on its own. Verification always happens on your server.

05Verify

Server verification

A token is worthless until your server checks it. POST the token and your secret key to the verify endpoint. The shape is plain and familiar — it maps onto whatever verification code you already have.

http
POST https://tilldev.dev/api/tillgate/siteverify
Content-Type: application/json

{
  "secret":   "tg_secret_…",
  "response": "<token from the tillgate-response field>"
}
200 OK
{
  "success":      true,
  "hostname":     "example.com",
  "challenge_ts": "2026-07-09T10:15:04Z",
  "error-codes":  []
}
  • successtrue only when the token is valid, unexpired, unredeemed, and its proof-of-work checks out.
  • hostname — the host the pass was issued for. Compare it to the request’s own host for defence in depth.
  • challenge_ts — when the challenge was solved, so you can enforce your own freshness window.
  • error-codes — empty on success; otherwise see error codes.
Server side
Treat a non-true success as “no human proven” and fall back to a failed check — reject, rate-limit, or re-challenge. Never accept the token client-side alone.
06Modes

The three modes

Every mode runs the same proof-of-work and issues the same signed pass. What differs is how much the visitor sees. Set it on the site key or per widget.

ModeBehavior
managedDefault. Usually runs silently and only escalates to a visible click-to-confirm step when the difficulty warrants it.
invisibleNo visible control. The proof-of-work runs in the background and the token is ready by submit time.
interactiveAlways renders an explicit control the visitor clicks to start the check. Best where the human step should be deliberate.
07Tuning

Difficulty & adaptive defense

Difficulty is the number of leading zero bits the hash must have. Each extra bit roughly doubles the expected work, so it is an exponential dial: small increases raise the cost sharply.

You set a baseline per site key, but TillGate also scales the challenge to risk automatically: a normal visitor gets your baseline, while a request that looks like automation is handed a harder, memory-hard challenge — or turned away outright. Real people stay fast; bots pay.

Africa-first
TillDev is Africa-first — keep the baseline modest on consumer-facing forms so you don’t tax low-end phones. Reserve higher difficulty and interactive mode for high-value actions (sign-up, checkout, password reset).
08Reference

Error codes

When success is false, the error-codes array explains why. The codes follow the familiar hyphenated convention:

CodeMeaning
missing-input-secretNo secret key was sent in the request.
invalid-input-secretThe secret key is malformed or doesn’t match a project.
missing-input-responseNo response token was sent.
invalid-input-responseThe token is malformed, its signature is bad, or its proof-of-work doesn’t validate.
timeout-or-duplicateThe token is past its TTL or has already been redeemed (replay).
hostname-mismatchThe token’s bound hostname doesn’t match the site key’s project.
internal-errorA transient error on our side. Retry the verification.
09Integration

WAF challenge

TillGate stands alone on any form, but it also complements the TillShield inline WAF. A rule whose mode is challenge can present TillGate instead of a bare 429: a request that trips a rate limit gets a chance to prove there’s a human behind it and continue, rather than being rejected outright. Same enforcement decision, softer edge.


TillGate is part of TillShield — one workspace, one login, one audit log. See the TillShield docs, or prefer the pitch? Read the product page.