<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[NoneCap]]></title><description><![CDATA[NoneCap]]></description><link>https://nonecap.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aec59f24037deea71044a7b/3ec4caef-b848-4f72-adcd-109c2f312e4f.png</url><title>NoneCap</title><link>https://nonecap.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 15 Nov 2026 19:02:12 GMT</lastBuildDate><atom:link href="https://nonecap.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[hCaptcha test sitekey: the official keys for integration testing]]></title><description><![CDATA[hCaptcha's official test sitekeys, secret and dummy tokens, a page to paste, and the not-using-dummy-secret error explained.]]></description><link>https://nonecap.hashnode.dev/hcaptcha-test-sitekey-the-official-keys-for-integration-testing</link><guid isPermaLink="true">https://nonecap.hashnode.dev/hcaptcha-test-sitekey-the-official-keys-for-integration-testing</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[hcaptcha]]></category><dc:creator><![CDATA[Daniel Carlsson]]></dc:creator><pubDate>Thu, 05 Nov 2026 10:48:28 GMT</pubDate><content:encoded><![CDATA[<p>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.</p>
<h2>The keys</h2>
<p>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, <code>0x0000000000000000000000000000000000000000</code>.</p>
<pre><code class="language-plaintext"># 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).
</code></pre>
<table>
<thead>
<tr>
<th>Key set</th>
<th>Test sitekey</th>
<th>Dummy response token</th>
</tr>
</thead>
<tbody><tr>
<td>Publisher / Pro (always passes)</td>
<td><code>10000000-ffff-ffff-ffff-000000000001</code></td>
<td><code>10000000-aaaa-bbbb-cccc-000000000001</code></td>
</tr>
<tr>
<td>Enterprise: safe end user</td>
<td><code>20000000-ffff-ffff-ffff-000000000002</code></td>
<td><code>20000000-aaaa-bbbb-cccc-000000000002</code></td>
</tr>
<tr>
<td>Enterprise: bot detected</td>
<td><code>30000000-ffff-ffff-ffff-000000000003</code></td>
<td><code>30000000-aaaa-bbbb-cccc-000000000003</code></td>
</tr>
</tbody></table>
<p>The two enterprise sets let you run both sides of your handling code: <code>20000000-…</code> stands in for a clean user, <code>30000000-…</code> for a detected bot. The docs also say to pass <code>remoteip</code> to siteverify when testing enterprise keys; without it some response fields, the risk score among them, stay switched off.</p>
<h2>What the test flow actually does</h2>
<p>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:</p>
<ul>
<li>Clicking the checkbox passes straight away. No image grid, no drag puzzle.</li>
<li>The <code>h-captcha-response</code> field receives the fixed dummy token (<code>10000000-aaaa-bbbb-cccc-000000000001</code> for the publisher key) instead of a real <code>P1_</code> token.</li>
<li>siteverify returns <code>success: true</code> for that token only when you verify it with the test secret.</li>
</ul>
<p>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.</p>
<h2>A page you can paste</h2>
<p>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:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;script src="https://js.hcaptcha.com/1/api.js" async defer&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;form action="/submit" method="POST"&gt;
      &lt;!-- Official test sitekey: renders the widget, never challenges. --&gt;
      &lt;div class="h-captcha"
           data-sitekey="10000000-ffff-ffff-ffff-000000000001"&gt;&lt;/div&gt;
      &lt;button type="submit"&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>On the server, verify whatever arrived in <code>h-captcha-response</code> against the test secret:</p>
<pre><code class="language-bash">curl "https://api.hcaptcha.com/siteverify" \
  -d "secret=0x0000000000000000000000000000000000000000" \
  -d "response=10000000-aaaa-bbbb-cccc-000000000001"
</code></pre>
<p>Response:</p>
<pre><code class="language-json">{
  "success": true,
  "credit": false,
  "hostname": "dummy-key-pass",
  "challenge_ts": "2026-11-01T19:02:29.372Z"
}
</code></pre>
<h2>The not-using-dummy-secret error</h2>
<p>Test values only work as a matched set. Combine one test value with one real value and siteverify fails with its own error code:</p>
<pre><code class="language-json">{
  "success": false,
  "error-codes": ["not-using-dummy-secret"]
}
</code></pre>
<p>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 <code>not-using-dummy-passcode</code>: "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.</p>
<p>Two other combinations behaved differently in the same run. With the test secret, a made-up <code>P1_</code> token came back as <code>invalid-input-response</code>, not this error. And the dummy token still verified when a different test sitekey was sent as the <code>sitekey</code> parameter.</p>
<p>The fix is mechanical: pick one row of the table and use its sitekey, the shared test secret and its dummy token together. <code>sitekey-secret-mismatch</code> ("The sitekey is not registered with the provided secret.") is the same mistake made with real keys.</p>
<blockquote>
<p><strong>Don't ship them.</strong> 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.</p>
</blockquote>
<h2>What the test keys can't tell you</h2>
<p>Most of what matters about hCaptcha in production never shows up in the test flow:</p>
<ul>
<li><strong>No real challenges.</strong> 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.</li>
<li><strong>No token lifecycle.</strong> The token is a constant string, so expiry (about 120 seconds), single use and already-seen responses are never exercised.</li>
<li><strong>No rqdata.</strong> There's no test path at all for enterprise <code>rqdata</code> binding. The enterprise test keys simulate risk verdicts, not the rqdata flow.</li>
</ul>
<p>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 <code>POST /v1/solves</code> and you get back a real <code>P1_</code> token that behaves like production, expiry and single use included.</p>
<pre><code class="language-bash">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"
  }'
</code></pre>
<p>Submit the returned token as <code>h-captcha-response</code> and your real secret validates it at siteverify. Request and response fields are in the <a href="https://nonecap.com/api-reference/">API reference</a>; on Node, the <a href="https://nonecap.com/sdk/">TypeScript SDK</a> wraps the submit-and-wait in one call.</p>
<p><em>Originally published at</em> <a href="https://nonecap.com/learn/hcaptcha-test-sitekey/"><em>nonecap.com</em></a><em>, with an FAQ on the not-using-dummy-secret error and whether a test key exists that always shows a challenge.</em></p>
]]></content:encoded></item></channel></rss>