Human verification
Portcullis
Portcullis is Zipper’s CAPTCHA alternative. It checks browser signals, solves a short proof-of-work, and issues a one-time token. Embed a site key, collect the token, and confirm it with Siteverify. Managed, non-interactive, and invisible modes. This is a Cloudflare Cloudflare Turnstile alternative included on every Zipper plan.
Cloudflare analogue: Cloudflare Turnstile
What it is
Portcullis is Zipper’s CAPTCHA alternative. It checks browser signals, solves a short proof-of-work, and issues a one-time token. Embed a site key, collect the token, and confirm it with Siteverify. Managed, non-interactive, and invisible modes.
Cloudflare analogue: Cloudflare Turnstile. SLA 99.95% · p99 180ms. Operators tune difficulty. Admins create widgets.
- ▸Animated widget
- ▸Proof-of-work and signals
- ▸One-time tokens
- ▸Siteverify API
How it works
Create a widget. You receive a public site key and a secret shown once.
Embed api.js and a div with data-sitekey. The widget collects signals, solves a short proof-of-work, and mints a one-time token.
POST that token to Siteverify with the secret. Never trust the browser alone.
Use cases
Concrete ways teams use this service on day one.
Signup and login forms
You need to stop fake accounts without a visual puzzle.
- Create a widget restricted to your hostname + localhost.
- Embed the snippet on /signup and /login.
- On submit, Siteverify the token before creating a session.
Public APIs abused by bots
A waitlist form is posted by scripts.
- Mode: managed. Challenge only when risk is high.
- Use Invisible if the form is already behind The Keep.
Set it up in the dashboard
Dashboard → Portcullis → Create widget.
Copy the secret. Embed the snippet from Docs → Portcullis.
API
Control-plane: POST /api/portcullis/v1/siteverify. Send Authorization: Bearer tz_live_YOUR_TOKEN.
Creates count against the plan quota. A 402 plan_limit means you are at the cap — upgrade or delete an unused resource.
Embed
<script src="https://tinyzipper.com/portcullis/v1/api.js" async defer></script> <form method="POST" action="/login"> <div class="tz-portcullis" data-sitekey="0xSITE_KEY" data-theme="dark"></div> <button type="submit">Enter</button> </form>
Siteverify
curl -sS -X POST https://tinyzipper.com/api/portcullis/v1/siteverify \
-H "Content-Type: application/json" \
-d '{"secret":"0xSECRET","response":"<token>"}'Node.js
const res = await fetch("https://tinyzipper.com/api/portcullis/v1/siteverify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ secret: process.env.PORTCULLIS_SECRET, response: token }),
});
const json = await res.json();
if (!json.success) throw new Error("Portcullis failed");List the same resource in JavaScript
const res = await fetch("https://tinyzipper.com/api/v1/whoami", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.ZIPPER_TOKEN}`,
"Content-Type": "application/json"
}
});
const json = await res.json();
if (!res.ok) throw new Error(json.error ?? res.statusText);
console.log(json);Tips
Tokens are single-use. A replay returns timeout-or-duplicate.
Demo key 0xDEMO with secret demo works on this origin for local tests.
- ▸SOC 2
- ▸GDPR
- ▸CCPA
Runbook
Fail closed if a token cannot be issued. Rotate site secrets from the Portcullis page.
Live demo
Runs against this origin with the platform demo site key.
Portcullis
ProtectedStarting verification…
ZipperNext: The Moat · All docs · Create a free account