How It Works
KEPCA uses a progressive profiling approach: gather passive signals first, then escalate to active challenges only when the risk score warrants it.
Request lifecycle
Every verification follows these eight steps:
Browser KEPCA API Your Server
| | |
| 1. Page load — collect | |
| passive signals | |
| | |
| 2. POST /v1/{sk}/init ------>| |
| { signals } | |
| | 3. Risk scoring |
| 4. { session_id, | |
| requires_challenge } | |
| <----------------------------| |
| | |
| 5. POST /v1/{sk}/challenge ->| (only if required) |
| <-- { challenge } | |
| | |
| 6. Solve PoW in Web Worker | |
| | |
| 7. POST /v1/{sk}/solve ----->| |
| <-- { token } | |
| | |
| 8. Form submit with token ---|----------------------------->|
| | |
| | POST /v1/siteverify <-------|
| | { secret, token, ip } |
| |------> { success, score } |Steps 5-7 are skipped for low-risk users (tier 1). They receive a token directly after step 4.
Risk scoring
The risk engine evaluates multiple signals and assigns a composite score from 0.0 (certainly human) to 1.0 (certainly bot):
| Signal | Weight | What it detects |
|---|---|---|
time_on_page_ms | High | Bots submit forms in < 1 second |
submit_time_ms | High | Time between page load and form submit |
honeypot_filled | Max | Only bots fill hidden fields |
user_agent | Medium | Known bot UA strings, headless browsers |
tls_fingerprint | Medium | TLS JA3/JA4 fingerprint anomalies |
request_count | Medium | Rapid repeated requests from same source |
ip_hash | Low | IP reputation (optional, privacy-safe) |
Risk tiers
| Tier | Score range | Action |
|---|---|---|
| 1 | 0.0 - 0.3 | Invisible pass — no challenge |
| 2 | 0.3 - 0.6 | Proof-of-Work challenge |
| 3 | 0.6 - 1.0 | Interactive challenge (grid, slider) |
The thresholds are configurable per site via the dashboard.
Challenge types
KEPCA supports six challenge types, activated per site:
Proof of Work (PoW)
A SHA-256 hash puzzle solved in a Web Worker. The difficulty scales with the risk score. Invisible to the user — they only see a brief spinner.
Checkbox
Classic "I'm not a robot" click. Triggers the full flow behind the scenes.
Slider
Drag a slider to a target position. Adds an interaction signal.
Grid
Select matching images from a grid. Used for high-risk traffic.
Interactive
Custom interactive puzzles (map-based, pattern-based). The most resistant to automated solving.
Text
Simple text-based challenges (math problems, word completion). Accessibility-friendly fallback.
Token structure
Tokens are self-contained and HMAC-signed (HS256). They can be verified without contacting the KEPCA API, enabling offline verification.
Format
base64url(header).base64url(payload).base64url(signature)Header
{
"alg": "HS256",
"v": 1
}Payload
{
"sk": "mpt_site_abc123",
"ts": 1712700000,
"exp": 1712700120,
"rs": 0.15,
"ch": "pow",
"ip": "sha256_of_client_ip",
"jti": "unique_token_id"
}| Field | Description |
|---|---|
sk | Site key the token was issued for |
ts | Issued-at timestamp (Unix seconds) |
exp | Expiry timestamp (120 seconds after issue) |
rs | Risk score at time of issue (0.0 - 1.0) |
ch | Challenge type that was solved |
ip | SHA-256 hash of the client IP (privacy-safe) |
jti | Unique token ID for replay prevention |
Offline verification
Because the token is self-contained, your backend can verify it locally using the shared HMAC secret without making a network call to KEPCA. The POST /v1/siteverify endpoint does the same thing but also logs analytics events.
