Prove there’s a human.
GATillGate 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.
Three moving parts
- 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.
- A signed pass token. On success the widget writes a short-lived, signed token into a form field named
tillgate-responseand calls yourdata-callback. The token is bound to your hostname and carries a short TTL. - 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.
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.
tg_secret_… key in client code, a bundle, or a repository. It belongs only in your backend’s /siteverify call.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.
<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).
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.
npm i @tilldev/tillgateimport { 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.
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.
POST https://tilldev.dev/api/tillgate/siteverify
Content-Type: application/json
{
"secret": "tg_secret_…",
"response": "<token from the tillgate-response field>"
}{
"success": true,
"hostname": "example.com",
"challenge_ts": "2026-07-09T10:15:04Z",
"error-codes": []
}success—trueonly 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.
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.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.
| Mode | Behavior |
|---|---|
managed | Default. Usually runs silently and only escalates to a visible click-to-confirm step when the difficulty warrants it. |
invisible | No visible control. The proof-of-work runs in the background and the token is ready by submit time. |
interactive | Always renders an explicit control the visitor clicks to start the check. Best where the human step should be deliberate. |
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.
interactive mode for high-value actions (sign-up, checkout, password reset).Error codes
When success is false, the error-codes array explains why. The codes follow the familiar hyphenated convention:
| Code | Meaning |
|---|---|
missing-input-secret | No secret key was sent in the request. |
invalid-input-secret | The secret key is malformed or doesn’t match a project. |
missing-input-response | No response token was sent. |
invalid-input-response | The token is malformed, its signature is bad, or its proof-of-work doesn’t validate. |
timeout-or-duplicate | The token is past its TTL or has already been redeemed (replay). |
hostname-mismatch | The token’s bound hostname doesn’t match the site key’s project. |
internal-error | A transient error on our side. Retry the verification. |
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.