---
type: explanation
---

# Hosted verification

A paid feature for customers without a backend. The customer points their form at a Caputchin URL instead of their own server; the platform verifies the wrapped token and forwards the submission to a webhook, an email address, or both.

The decision and rationale (paid-only, no submission storage, webhook + email scope) live in [ADR-0007](adr/0007-hosted-verification-paid-only.md). This page covers what the feature is, who it's for, how it fits the system, and what it deliberately is not.

## Who it's for

- Static sites (Webflow, Framer, plain HTML on a CDN)
- No-code builders (Wix, Squarespace, Carrd)
- Solo developers and small teams who don't want to run a backend just to handle a contact form
- Anyone using Caputchin who finds setting up server-side verification a hassle

## How it changes the integration

The customer-visible HTML barely changes. Same `<caputchin-widget>` element, same form fields. The only difference is the form's `action` attribute — it points at a Caputchin forwarder URL instead of the customer's own backend. See the [setup guide](guides/setup-hosted-verification.md) for the walkthrough.

## Where it sits in the system

The forwarder is a **route inside the web app** on the `forward.caputchin.com` subdomain — same Cloudflare deploy, same database, same code base as the dashboard and Platform API. It is a distinct *product surface* (own customer contract, own billing gate, own [trust-model row](architecture.md#trust-model)) but not a separate operational service. See [deployment](deployment.md#hosted-services) and [ADR-0010](adr/0010-cloudflare-only-stack.md).

The route owns: receiving the form POST, calling `/siteverify` internally with the platform's own secret, dispatching to the configured destinations (webhook via signed `fetch`, email via Resend). It does **not** own: storing the submission, retrying indefinitely, or transforming the payload beyond what each destination requires.

## End-to-end flow

```mermaid
sequenceDiagram
  autonumber
  participant U as User
  participant B as Browser (caputchin widget)
  participant F as Caputchin forwarder
  participant V as /siteverify
  participant D as Customer destination<br/>(webhook or email)

  U->>B: Fill form, submit
  B->>F: POST form payload (incl. caputchin-token)
  F->>V: Verify token (platform's own secret)
  V-->>F: Verification result
  alt Token valid
    F->>D: Deliver submission (signed POST or email)
    D-->>F: Acknowledge
    F-->>B: 200 OK (redirect / thank-you)
  else Token invalid or missing
    F-->>B: 400 (rejected)
  end
```

The forwarder holds the submission in memory only for the duration of the dispatch. There is no persistence layer between steps 2 and 4 — see [ADR-0007](adr/0007-hosted-verification-paid-only.md).

## Destinations

Customers configure one or both per site key. See the [dashboard](dashboard.md#paid-tier-surfaces) for where this lives.

| Destination | What customer receives | Delivery shape |
|---|---|---|
| **Webhook** | Signed `POST` containing the form payload plus verification metadata | Customer's handler verifies the signature using a webhook signing key issued by the dashboard, then processes the submission |
| **Email** | Plain email with the form fields and a footer noting Caputchin verified the submission | Sent via Resend; customer sees a clean inbox-friendly message |

Both can be enabled simultaneously on a single site key — useful for "log to webhook for processing, email me a copy" patterns.

## Rate limiting

Caputchin applies rate limits per site key to protect both our forwarder infrastructure and the customer's downstream destinations from overload. The limits are not customer-configurable — sensible defaults per pricing tier, set by Caputchin and adjusted as we observe abuse patterns.

## Privacy stance

The forwarder reinforces rather than weakens the [structural privacy posture](privacy.md):

- **Submission contents are not stored.** They exist in process memory for the duration of the forward, then go.
- **No analytics on submitters.** The forwarder does not collect submitter IPs, user agents, geolocation, or any per-user data — same posture as the rest of the platform.
- **Forwarder logs carry no payload data.** Aggregate counters and error codes only, mirroring the [aggregate-stats model](dashboard.md#integration-health-diagnostics).

See the [privacy doc](privacy.md#what-we-do-not-collect) for the full inventory.

## What is intentionally absent

- **No submission storage or "submission inbox" UI.** Build a record on the webhook end if you want one.
- **No first-party Discord / Slack / Telegram / SMS adapters.** Webhook + email only. Customers wanting more shapes use Zapier, Make, or their own webhook handler.
- **No custom forwarder domains at MVP.** Forwarder URLs live under our domain; custom domains stay [deferred entirely](roadmap.md#whats-deferred-entirely) as a future enterprise tier.
- **No free tier.** Paid only — see [ADR-0007](adr/0007-hosted-verification-paid-only.md) for why.
- **No customer-configurable rate limits.** Defaults per pricing tier; we own the knob.
- **No payload transformation.** What the form posts is what the webhook receives (plus verification metadata). No field mapping, templating, or schema enforcement.

## Why it works as a paid feature

Hosted verification removes the largest remaining adoption objection ("I don't have a backend"). On the free tier the customer hits that wall and has a clear upgrade path: pay for hosted verification and ship in minutes. The feature has real marginal cost (forwarder runtime, email delivery via Resend) that maps cleanly onto subscription pricing, and the paid gate filters spammers out of the customer base.

## Brand fit

Same posture as the rest of the platform: privacy-first (no submission storage), honest about what's verified (uses the same `/siteverify` everyone else uses, just with the platform's own secret on the customer's behalf), and opinionated about scope (webhook + email, not everything). See [principles](principles.md).
