Keys
Put the site key in VITE_FMK_SITE_KEY (or your bundler's public variable). The secret stays on the server.
FitMyKB Verify
A 30-line component loads the widget once, renders it with fmkcaptcha.render and hands your form the token. No npm package to install.
Put the site key in VITE_FMK_SITE_KEY (or your bundler's public variable). The secret stays on the server.
It loads api.js with render=explicit the first time it mounts, renders the box into a div, and removes it on unmount, so it behaves well with React's strict mode and route changes.
// FitMyKBCaptcha.tsx: loads the widget once and hands you the token
import { useEffect, useRef } from "react";
declare global { interface Window { fmkcaptcha?: any } }
let loading: Promise<any> | null = null;
function loadApi(): Promise<any> {
loading ??= new Promise((resolve) => {
const script = document.createElement("script");
script.src = "https://captcha.fitmykb.com/api.js?render=explicit";
script.async = true;
script.onload = () => resolve(window.fmkcaptcha);
document.head.append(script);
});
return loading;
}
export function FitMyKBCaptcha({ siteKey, onToken }: { siteKey: string; onToken: (token: string) => void }) {
const box = useRef<HTMLDivElement>(null);
useEffect(() => {
let id: string | undefined;
let gone = false;
loadApi().then((fmk) => {
if (!gone && box.current) {
id = fmk.render(box.current, { sitekey: siteKey, callback: onToken, "expired-callback": () => onToken("") });
}
});
return () => { gone = true; if (id !== undefined) window.fmkcaptcha?.remove(id); };
}, [siteKey]);
return <div ref={box} />;
}The widget adds fmk-captcha-response inside the form, so new FormData(form) already carries the token. The button waits until the visitor has passed.
// SignUp.tsx
import { useState } from "react";
import { FitMyKBCaptcha } from "./FitMyKBCaptcha";
export function SignUp() {
const [token, setToken] = useState("");
async function submit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
// The widget adds fmk-captcha-response inside the form, so FormData already carries the token.
const res = await fetch("/api/signup", { method: "POST", body: new FormData(event.currentTarget) });
if (!res.ok) setToken(""); // tokens work once: the widget resets for the next try
}
return (
<form onSubmit={submit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<FitMyKBCaptcha siteKey={import.meta.env.VITE_FMK_SITE_KEY} onToken={setToken} />
<button disabled={!token}>Create account</button>
</form>
);
}Check the token with siteverify before you create anything: the Node.js guide has the code. Tokens work once, so after a failed request the widget gives a fresh one.
For the App Router, the Next.js guide uses next/script and a server action instead.