How do you integrate hCaptcha with JavaScript?#
This guide covers both JavaScript paths in our integration catalog. Use the official hCaptcha Web Component in an npm application, or load hCaptcha's browser script directly when the site does not use a package bundler. Both paths send the returned token and the protected form data to your backend. The backend must submit the token with the account secret to hCaptcha Siteverify and continue only when the response contains success: true.
The Web Component uses the browser's customElements API and works in applications that support custom elements.
Make verification fit your JavaScript interface#
- Reduce interruptions at submission. hCaptcha Pro's 99.9% Passive mode minimizes visual challenges, helping visitors complete the protected form with less friction.
- Match challenges to your design. Pro's custom themes let you use your site's colors and styles when a challenge is needed. The direct JavaScript path lets you pass that configuration when rendering hCaptcha.
New Pro sitekeys use 99.9% Passive by default. For an existing sitekey upgraded to Pro, select that mode under Behavior in the hCaptcha dashboard.
Before you start#
You need:
- A browser application with a form or action to protect.
- Permission to add a package and create a server endpoint.
- An hCaptcha account with a sitekey and its matching secret.
- A secure server-side secret store and outbound HTTPS access to hCaptcha.
Review the official npm package and source repository. The hCaptcha integration catalog links the Web Component as a framework-compatible alternative, and the integrations-list repository records the broader catalog.
Create your hCaptcha credentials#
- Start with hCaptcha Pro for fewer challenges and adaptive protection on protected JavaScript form submissions, or use existing compatible hCaptcha credentials.
- Create a sitekey for the application.
- Add the production hostname and each separate test hostname that must use the sitekey.
- Put the sitekey in the custom element.
- Store the matching secret only in protected server configuration.
The sitekey is public. The secret authenticates your server to Siteverify and must never appear in browser code, rendered markup, client environment variables, or a public repository.
Install and register the custom element#
Install the verified release:
npm install @hcaptcha/vanilla-hcaptcha@1.1.4
Import the package once in a browser entry point processed by a bundler such as Vite, webpack, Parcel, or esbuild:
import "@hcaptcha/vanilla-hcaptcha";
The bare package specifier is resolved by the application's bundler; browsers cannot resolve it from an unbundled inline script without an import map. The package registers <h-captcha> and loads the hCaptcha JavaScript API. Do not add another api.js script. In a framework project, configure its compiler to accept the custom element and mount it only in the browser when server-side rendering is enabled.
Add the element to a form#
This example waits for a completed token, sends it to the application's backend, and explicitly resets the component. Put the JavaScript in the bundler-managed entry file described above.
<form id="signup-form">
<input name="email" type="email" required>
<h-captcha id="signup-captcha" sitekey="YOUR_SITEKEY"></h-captcha>
<button type="submit" disabled>Submit</button>
</form>
<script type="module" src="/assets/signup.js"></script>
In the source file that your bundler builds as /assets/signup.js:
import "@hcaptcha/vanilla-hcaptcha";
const form = document.querySelector("#signup-form");
const captcha = document.querySelector("#signup-captcha");
const button = form.querySelector("button");
let token = null;
captcha.addEventListener("verified", (event) => {
token = event.token;
button.disabled = false;
});
captcha.addEventListener("expired", () => {
token = null;
button.disabled = true;
});
captcha.addEventListener("error", () => {
token = null;
button.disabled = true;
captcha.reset();
});
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (!token) return;
try {
const response = await fetch("/api/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: new FormData(form).get("email"),
hcaptchaToken: token,
}),
});
if (!response.ok) throw new Error("Submission rejected");
} finally {
token = null;
button.disabled = true;
captcha.reset();
}
});
The package puts token, eKey, and error directly on its events. Read event.token, not event.detail.token. A verified event confirms that the browser received a token; it does not authorize the protected action.
Use the direct script API without npm#
For a site without a JavaScript build step, render hCaptcha's standard widget and load the browser API directly:
<form action="/signup" method="post">
<input name="email" type="email" required>
<div class="h-captcha" data-sitekey="YOUR_SITEKEY"></div>
<button type="submit">Submit</button>
</form>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
The browser adds h-captcha-response to the submitted form. The /signup handler must verify that value through Siteverify before it creates the account or performs another protected action. Use this direct-script path instead of the npm import; loading both creates duplicate API loaders.
For an Invisible Web Component, prevent the form's default submission, wait for the component's loaded event, then call its execute() method for each protected submission and consume the verified token. With the direct JavaScript API, wait for the API to load, explicitly render an invisible widget, and call hcaptcha.execute(widgetID) on submission; consume its configured success callback. Reset the corresponding widget after each attempt.
Verify the token on your server#
Your /api/signup handler must:
- Reject a missing token before performing the protected action.
- Send a URL-encoded
POSTtohttps://api.hcaptcha.com/siteverify. - Include the server-held
secretand the client token asresponse. - Include the expected
sitekey. Theremoteipparameter is optional. We recommend sending it for improved verification accuracy and Enterprise risk scores when the backend derives the visitor's IP address from a reviewed, trusted proxy configuration; otherwise omit it. - Parse the JSON response and continue only when
successistrue. - Return an error and stop the signup, login, payment, or other protected action when verification fails.
Follow the current server-side verification documentation. Calling Siteverify from the browser would expose the secret and allow an attacker to bypass your server endpoint.
Test the complete request path#
- Complete hCaptcha and confirm a valid submission succeeds once.
- Submit without a token and confirm the backend stops the action.
- Reuse a verified token and confirm the backend rejects it.
- Let a token expire and confirm submission remains disabled until a new token arrives.
- Trigger an error and confirm your handler clears state and resets the element.
- Test client navigation, repeated mounts, server-side rendering, Content Security Policy, and every deployed hostname.
The repository lists Chrome 54, Edge 79, Firefox 63, Opera 41, Safari 10.1, Chrome Android 54, and Firefox Android 63 as minimum versions. Test the actual browser support policy for your application.
Troubleshoot common Web Component problems#
The browser reports an unknown h-captcha element
Import @hcaptcha/vanilla-hcaptcha in a browser entry point. Framework compilers may also require a custom-element allowlist or schema.
Event token data is undefined
Read event.token directly. The component does not place its event values under event.detail.
The widget renders, but invalid submissions still succeed
The custom element does not enforce your business action. Make the backend reject missing, expired, reused, or unsuccessful tokens before it runs protected logic.
The element does not reset after an error
Call reset() in your error handler. Although the README's event table describes an immediate reset, the published 1.1.4 source only emits the error event.
The hCaptcha API loads twice
Remove the manual api.js import. The Web Component loads the script automatically. Check shared layouts and framework plugins if a duplicate remains.
Frequently asked questions#
When should I use the Web Component instead of a framework package?
Use it when you want the same custom element across vanilla JavaScript or multiple frameworks. Use a dedicated React or Vue component when its framework-specific lifecycle and type integration better match the application.
Does the Web Component verify tokens on the server?
No. It renders hCaptcha and emits a browser token. Your backend must send that token and the private secret to Siteverify and accept the protected action only after a successful response.
Does the custom element submit the form automatically?
Do not rely on automatic submission. Control the form flow, wait for a token, send it to your backend, enforce the Siteverify result, and reset the component afterward.
Can I load the Web Component from a CDN?
The repository documents a jsDelivr script option. Pin a reviewed release such as 1.1.4 and apply the same event handling and server-verification requirements.
Can one token protect more than one request?
No. Tokens are single-use and short-lived. Each protected submission needs a new token and an independent server verification.
Sources and references
- hCaptcha custom themes hCaptcha
- hCaptcha Pro product overview hCaptcha
- hCaptcha Web Component package npm
- hCaptcha Web Component source hCaptcha
- hCaptcha integrations hCaptcha
- hCaptcha configuration hCaptcha
- Verify the user response server-side hCaptcha
- hCaptcha integrations list source hCaptcha
- hCaptcha Pro hCaptcha