---
type: explanation
title: Verify without a backend
summary: What hosted verification is, who it is for, and how Caputchin verifies a submission and forwards it to a webhook or email so you never run a backend.
---

A Caputchin token proves nothing until something confirms it. Normally that something is [your own backend](/docs/integration-guides/verify-on-your-backend), which calls `/siteverify` and decides whether to trust the request. Hosted verification moves that step onto Caputchin: your form posts to a Caputchin forwarder, Caputchin verifies the token, drops anything that fails, and forwards only the verified submissions to a destination you choose.

It is the same trust decision as the backend check, run on Caputchin's side instead of yours. The point is that there is no `/siteverify` call to write and no secret to keep on a server, because there is no server.

Hosted verification is a paid feature, available on Alpha and up.

## Who it is for

Hosted verification exists for the case where running a backend just to check a token is more than the project needs.

| You are | Why it fits |
|---|---|
| A static site (Webflow, Framer, plain HTML on a CDN) | There is no server to receive the form or call `/siteverify`. |
| A no-code builder (Wix, Squarespace, Carrd) | You cannot add server-side verification code. |
| A solo developer or small team | A contact or signup form is not worth standing up and operating a backend for. |
| Already running a backend, but not for this form | You can point one form at the forwarder and leave the rest of your stack alone. |

If you do run a backend and want to verify there, use [verify on your backend](/docs/integration-guides/verify-on-your-backend) instead. The two are alternatives, not layers.

## How it works

```mermaid
sequenceDiagram
  autonumber
  participant U as Visitor
  participant B as Browser (widget)
  participant F as Caputchin forwarder
  participant D as Your destination (webhook or email)

  U->>B: Fill the form and submit
  B->>F: POST form fields plus the caputchin token
  Note over F: Verify the token, then drop it from the payload
  alt Token valid
    F->>D: Deliver the submission
    D-->>F: Acknowledge
    F-->>B: 200 with delivery result
  else Token missing or invalid
    F-->>B: Rejected, nothing delivered
  end
```

The forwarder holds the submission in memory only for the moment it takes to deliver it. There is no storage between the post and the delivery, and the `caputchin-token` field is stripped before the payload reaches your destination.

## Destinations

You configure one or both per site key. Enabling both is a common pattern: send to a webhook for processing and email yourself a copy.

| Destination | What you receive |
|---|---|
| **Webhook** | A JSON `POST` carrying your form fields plus Caputchin's verification metadata. |
| **Email** | A plain email with the form fields and a footer noting Caputchin verified the submission. |

Deliveries are not signed. The forwarder URL's secrecy is what authenticates a webhook call, so keep the URL out of public repositories and client-side code. The [setup tutorial](/docs/hosted-verification/set-up) shows the exact webhook payload.

## What is kept private

Hosted verification follows the same no-visitor-data posture as the rest of Caputchin:

- **Submissions are never stored.** They live in process memory for the duration of the forward, then they are gone. Build your own record on the webhook end if you want one.
- **No data about the submitter is collected.** No IP, no User-Agent, no fingerprint, no tracking.
- **Telemetry is aggregate only.** Caputchin counts delivery successes and failures so you can see whether your destinations are healthy, never the contents of any submission. See [statistics](/docs/hosted-verification/statistics).

## How a customer-supplied URL is kept safe

A URL that Caputchin's servers will call is a classic Server-Side Request Forgery risk. The forwarder rejects any webhook URL whose host is a private, loopback, link-local, or cloud-metadata address, both when you save it and again right before every delivery. The blocked categories include:

| Category | Examples |
|---|---|
| Loopback and unspecified | `127.0.0.1`, `0.0.0.0`, `::1` |
| Private (RFC 1918) | `10.x.x.x`, `172.16.x.x` through `172.31.x.x`, `192.168.x.x` |
| Link-local and cloud metadata | `169.254.x.x`, in particular the metadata endpoint `169.254.169.254` |
| Carrier-grade NAT | `100.64.x.x` through `100.127.x.x` |
| Multicast and reserved | `224.x.x.x` and above |
| Internal hostnames | `localhost` and any host ending in `.local`, `.localhost`, or `.internal` |
| Private IPv6 | link-local (`fe80::/10`) and unique-local (`fc00::/7`) addresses |

Encodings that try to disguise a blocked address are caught too: a decimal-integer host like `http://2130706433/` or a hex host like `http://0x7f000001/` (both of which mean `127.0.0.1`) are rejected. Only `http` and `https` URLs are accepted; any other scheme is refused. The forwarder also refuses to follow redirects, so a webhook endpoint cannot bounce the call to an internal target. A blocked URL surfaces as a generic error rather than a specific one, so it cannot be used to probe a network. Your webhook must therefore live at a public `https` URL.

## What is intentionally left out

- **No submission inbox.** There is no stored history of submissions to browse in the dashboard.
- **No first-party Discord, Slack, Telegram, or SMS adapters.** Webhook and email only. A webhook can fan out to any of those on your side.
- **No file uploads.** The forwarder accepts text fields; a submission carrying a file is rejected.
- **No payload transformation.** What the form posts is what your destination receives, plus the verification metadata.

## See also

- [Set up hosted verification](/docs/hosted-verification/set-up): a walkthrough that wires it to a test inbox first.
- [Hosted verification statistics](/docs/hosted-verification/statistics): reading delivery health.
- [Verify on your backend](/docs/integration-guides/verify-on-your-backend): the alternative when you do run a server.
