Caputchin
Getting Started

Add the widget to your site

You have BananaSeed's public key from the previous lesson. Now we put the widget on the React contact form, watch the dashboard confirm a real visitor cleared it, and then add the one server call that turns that into real protection.

What you will have at the end

  • The widget on your contact form, verifying visitors in the browser.
  • Your dashboard showing those verifications.
  • Your Node backend dropping any submission that has no valid token.

1. Add the widget to your React form

Install the package. It registers the <caputchin-widget> element (and the <caputchin-game> element for the next lesson):

npm install @caputchin/widget

Import it once at startup, such as in your app entry (main.jsx):

import "@caputchin/widget";

Now drop the widget into BananaSeed's contact form. When the checkbox clears, the widget adds a hidden caputchin-token field to the form, so you read it on submit exactly like your own fields. Replace cpt_pub_... with your public key:

export function ContactForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    await fetch("/api/contact", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        email: data.get("email"),
        message: data.get("message"),
        token: data.get("caputchin-token"),
      }),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <textarea name="message" required />
      <caputchin-widget sitekey="cpt_pub_..." />
      <button type="submit">Send</button>
    </form>
  );
}

On a plain HTML page with no build step, or inside a native mobile app? See the script-tag (CDN) integration and the mobile app guide.

2. See it verify

Run your app and open the contact form. The Caputchin checkbox appears and completes on its own, with no click required. You do not even need to submit the form yet: that is already a real verification, and it has reached Caputchin.

3. Confirm it in your dashboard

Back in the dashboard, open your troop and click into your site key. Its statistics show a funnel: Sessions started, Client-completed, Server-verified, and Threats blocked. Refresh the page; Sessions started and Client-completed both now read at least 1. That is your visit: the widget loaded (started) and the challenge was solved in the browser (client-completed), with no backend involved at all.

Server-verified is still 0, because nothing on your server has checked a token yet. That gap is the next step.

Your widget is live, and the dashboard is already logging real verifications, before you have written a line of backend code. 🎉

4. Verify the token on your backend

Client-completed means the visitor cleared the challenge, but your form is not protected until your server confirms the token. A bot can ignore the widget entirely and POST straight to your endpoint. So your backend checks every token with Caputchin before trusting the request. Your secret (cpt_sec_...) lives in an environment variable, never in the browser:

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/contact", async (req, res) => {
  const { email, message, token } = req.body;

  const verdict = await fetch("https://caputchin.com/api/v1/siteverify", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      secret: process.env.CAPUTCHIN_SECRET,
      response: token,
    }),
  }).then((r) => r.json());

  if (!verdict.success) {
    return res.status(400).json({ error: "Could not verify you are human." });
  }

  // Verified. Handle the real message: store it, email it, whatever you do.
  res.json({ ok: true });
});

app.listen(3001);

That verdict.success check is the point: a bot posting straight to /api/contact carries no token, success comes back false, and the message is dropped before you ever touch it.

No backend? No problem. Turn on hosted verification and Caputchin runs this check for you, forwarding only verified submissions to a webhook or your inbox.

5. Confirm the server check

Submit the contact form once more, now that your endpoint is verifying. Go back to your site key's statistics and refresh: Server-verified now reads at least 1 too, sitting alongside Started and Client-completed. The whole funnel is accounted for, browser to server.

To read that funnel the other way (what the gaps between the counts tell you about visitor drop-off and integration mistakes), see statistics and sessions.

Your contact form is genuinely protected now: every submission is checked on your server, and tokenless bots are turned away. 🎉

What just happened

StepWhereResult
<caputchin-widget> in your formReactThe checkbox verifies on mount and adds a one-time caputchin-token field.
Open the pageBrowserStarted and Client-completed tick, no backend needed.
/siteverify with your secretNode to Caputchinsuccess: true lets the request through; no token means it is dropped, and Server-verified climbs.

Next

The checkbox works. Now the fun part: swap it for a real playable game, with no change to your backend.

Continue to Add a game from the marketplace.

On this page