hCaptcha test sitekey: the official keys for integration testing
If your signup or contact form uses hCaptcha, your test suite has a problem: somebody (or something) has to get through the captcha on every run. hCaptcha's answer is a set of official test keys. Wire them in and the widget renders, passes on click, and hands back a fixed dummy token that siteverify accepts. Here's the full set, a page you can paste, and the one error you'll hit if the keys get mixed up.
The keys
hCaptcha's docs list one publisher key set and two enterprise variants. All three sitekeys render a widget that never shows a challenge, each one emits its own constant dummy token, and all three tokens verify against the same test secret, 0x0000000000000000000000000000000000000000.
# Publisher / Pro account keys: widget always passes, never challenges
sitekey: 10000000-ffff-ffff-ffff-000000000001
secret: 0x0000000000000000000000000000000000000000
token: 10000000-aaaa-bbbb-cccc-000000000001
# Enterprise: simulates a safe end user
sitekey: 20000000-ffff-ffff-ffff-000000000002
token: 20000000-aaaa-bbbb-cccc-000000000002
# Enterprise: simulates a detected bot
sitekey: 30000000-ffff-ffff-ffff-000000000003
token: 30000000-aaaa-bbbb-cccc-000000000003
# All three sets verify against the same test secret (the 0x00... one above).
| Key set | Test sitekey | Dummy response token |
|---|---|---|
| Publisher / Pro (always passes) | 10000000-ffff-ffff-ffff-000000000001 |
10000000-aaaa-bbbb-cccc-000000000001 |
| Enterprise: safe end user | 20000000-ffff-ffff-ffff-000000000002 |
20000000-aaaa-bbbb-cccc-000000000002 |
| Enterprise: bot detected | 30000000-ffff-ffff-ffff-000000000003 |
30000000-aaaa-bbbb-cccc-000000000003 |
The two enterprise sets let you run both sides of your handling code: 20000000-… stands in for a clean user, 30000000-… for a detected bot. The docs also say to pass remoteip to siteverify when testing enterprise keys; without it some response fields, the risk score among them, stay switched off.
What the test flow actually does
The widget loads like normal, but the round is scripted. In the docs' words, the keypair "will never challenge and always produce the same response token". In practice:
- Clicking the checkbox passes straight away. No image grid, no drag puzzle.
- The
h-captcha-responsefield receives the fixed dummy token (10000000-aaaa-bbbb-cccc-000000000001for the publisher key) instead of a realP1_token. - siteverify returns
success: truefor that token only when you verify it with the test secret.
Since the token never changes, the whole loop is deterministic, and that's the point. What you're testing is that your form posts the field, your backend calls siteverify, and both your success and failure branches run. You aren't testing hCaptcha.
A page you can paste
Save this as an HTML file and serve it from anywhere, localhost included (test keys skip the domain check). The widget renders, the checkbox passes, and the form submits the dummy token:
<!DOCTYPE html>
<html>
<head>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
</head>
<body>
<form action="/submit" method="POST">
<!-- Official test sitekey: renders the widget, never challenges. -->
<div class="h-captcha"
data-sitekey="10000000-ffff-ffff-ffff-000000000001"></div>
<button type="submit">Submit</button>
</form>
</body>
</html>
On the server, verify whatever arrived in h-captcha-response against the test secret:
curl "https://api.hcaptcha.com/siteverify" \
-d "secret=0x0000000000000000000000000000000000000000" \
-d "response=10000000-aaaa-bbbb-cccc-000000000001"
Response:
{
"success": true,
"credit": false,
"hostname": "dummy-key-pass",
"challenge_ts": "2026-11-01T19:02:29.372Z"
}
The not-using-dummy-secret error
Test values only work as a matched set. Combine one test value with one real value and siteverify fails with its own error code:
{
"success": false,
"error-codes": ["not-using-dummy-secret"]
}
That's what live siteverify returned on 2 November 2026 when the dummy token was sent with a secret other than the test one. hCaptcha's docs still call this error not-using-dummy-passcode: "You have used a testing sitekey but have not used its matching secret." The usual cause is the test sitekey on the page and your real secret on the server: the widget hands over the dummy token, and a real secret can't validate it.
Two other combinations behaved differently in the same run. With the test secret, a made-up P1_ token came back as invalid-input-response, not this error. And the dummy token still verified when a different test sitekey was sent as the sitekey parameter.
The fix is mechanical: pick one row of the table and use its sitekey, the shared test secret and its dummy token together. sitekey-secret-mismatch ("The sitekey is not registered with the provided secret.") is the same mistake made with real keys.
Don't ship them. Test keys give you zero bot protection. The sitekey, secret and dummy token are all public, so anyone can get through your form while they're live. hCaptcha's docs say to keep them strictly in test environments. Load the key from per-environment config so production can never start with the all-zeros secret.
What the test keys can't tell you
Most of what matters about hCaptcha in production never shows up in the test flow:
- No real challenges. The dummy round never renders one, so you learn nothing about the challenge types your users will meet, how often a challenge runs to several rounds, or how your UI copes while the iframe is open.
- No token lifecycle. The token is a constant string, so expiry (about 120 seconds), single use and already-seen responses are never exercised.
- No rqdata. There's no test path at all for enterprise
rqdatabinding. The enterprise test keys simulate risk verdicts, not the rqdata flow.
To close that gap, point staging at a real sitekey (your production one, or a second key you register) and use real tokens. If you don't want a person clicking through challenges on every CI run, you can mint the token with NoneCap, a captcha solver service that focuses on hCaptcha: send the sitekey and page URL to POST /v1/solves and you get back a real P1_ token that behaves like production, expiry and single use included.
curl "https://api.nonecap.com/v1/solves?wait=90" \
-H "Authorization: Bearer $NONECAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "hcaptcha",
"sitekey": "f5ab1c2d-7e8f-4a9b-b1c2-d3e4f5a6b7c8",
"url": "https://staging.your-site.example/signup"
}'
Submit the returned token as h-captcha-response and your real secret validates it at siteverify. Request and response fields are in the API reference; on Node, the TypeScript SDK wraps the submit-and-wait in one call.
Originally published at nonecap.com, with an FAQ on the not-using-dummy-secret error and whether a test key exists that always shows a challenge.
About the author
- Founder of NoneCap, a captcha solver service that focuses on hCaptcha.
Comments
Join the discussionNo comments yet. Be the first to comment.
