> ## Documentation Index
> Fetch the complete documentation index at: https://docs.erdo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Lead form protection

> Enable optional managed Turnstile verification for selected public lead forms while retaining passive capture observations.

Cloudflare Turnstile protection is **off by default** and enabled per event pipeline.
Use it for selected public lead forms when you want verification before capture.
[Passive capture observations](/lead-capture-observations) continue to be collected
independently; enabling protection does not add IP reputation scoring or automatic
ranking of leads.

## What visitors experience

Erdo prepares the Managed widget in the page hosting the form, while the visitor
fills it out. The widget uses `interaction-only` appearance: it becomes visible
when Cloudflare requires interaction. There is no checkbox for every visitor.
A submission can still wait for a token or ask the visitor to complete verification;
this is not a guarantee of zero delay.

Erdo handles the widget and token submission for hosted page forms. You do not
need to add a widget or credentials to the generated page's code. Forms whose
pipelines have protection disabled do not load Turnstile for that pipeline.

## Create a Managed widget

1. Open [Cloudflare Turnstile](https://dash.cloudflare.com/?to=/:account/turnstile),
   select your account, and choose **Add widget**.
2. Name the widget and add the hostnames where visitors open your public form.
   For example, a page at `https://homes.example.com/p/abc` needs
   `homes.example.com`. Add `pages.erdo.ai` only if visitors also use that public
   origin. Use the page's hostname, not `api.erdo.ai` or an iframe URL.
3. Select **Managed** mode and create the widget. Copy its **sitekey** and
   **secret key** into the local configuration file below.

See [Cloudflare's widget setup guide](https://developers.cloudflare.com/turnstile/get-started/widget-management/dashboard/)
for the dashboard steps. Turnstile can be used without moving your site's DNS or
proxying the site through Cloudflare. See [Cloudflare's overview](https://developers.cloudflare.com/turnstile/get-started/).

## Enable a pipeline

First inspect the pipeline and confirm it captures the intended page's leads:

```bash theme={null}
erdo event-pipelines list
erdo event-pipelines get acme-leads-a1b2c3d4
```

Create a local `turnstile.json` file containing your widget settings:

```json theme={null}
{
  "enabled": true,
  "site_key": "YOUR_MANAGED_WIDGET_SITEKEY",
  "secret": "YOUR_WIDGET_SECRET_KEY",
  "hostnames": ["homes.example.com"]
}
```

The hostname list is checked again by Erdo when verifying tokens. Supply the exact
hostnames visitors use, without a scheme, port, path, or wildcard, and allow them
in your Cloudflare widget too. Each enabled configuration requires a site key and
at least one hostname.

```bash theme={null}
chmod 600 turnstile.json
erdo event-pipelines update acme-leads-a1b2c3d4 --turnstile @turnstile.json
erdo event-pipelines get acme-leads-a1b2c3d4
```

Keep the credentials file out of source control. The secret is encrypted in Erdo
and omitted from read responses. Public pages receive only the public widget
settings. For later changes, include `enabled`, `site_key`, and `hostnames`; omit
`secret` to retain the existing encrypted secret, or supply it to replace the
secret. Omitting `--turnstile` leaves the protection policy unchanged.

Protection requires an artifact-bound `browser_form` pipeline with
`public_artifact` or `authenticated_viewer` authorization. For a public form using
`authenticated_viewer`, its existing audience must allow public visitors.
Reserved internal pipelines, including page analytics and widget events, cannot
enable this form protection.
Enabling protection does not change the pipeline's audience, state, transform,
or capture actions.

## Verification and outages

| Result                                                                                              | Capture behavior                                                                             |
| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Valid token with the expected hostname and pipeline binding                                         | Capture proceeds with `turnstile_status = "verified"`.                                       |
| Missing, invalid, expired, or already-used token; wrong hostname or pipeline binding                | Verification is required before capture. The hosted form can obtain a fresh token and retry. |
| Server cannot complete verification because of a provider, network, or server configuration failure | Capture is retained with `turnstile_status = "unavailable"`, rather than labelled verified.  |
| Protection disabled                                                                                 | Capture follows its existing behavior without a Turnstile status.                            |

A browser script failing to load is not proof of a server-observed outage and does
not bypass verification. The visitor may need to retry. A verified token is not
proof that the contact details are correct or that the person intends to buy.
Continue comparing [capture observations](/lead-capture-observations) with sales
outcomes.

After enabling, test the form on each allowed hostname. Check the pipeline's
[executions and destination dataset](/event-pipelines#managing-your-pipelines) to
confirm the expected capture and status. Inspect `unavailable` records as
unverified captures when reviewing quality. The nightly lead-health check keeps
watching a protected form: its synthetic submission runs inside the server, so it
is exempt from the challenge and still proves the form's transform and its
destination dataset work. It records no verification status, and it changes
nothing about what a real visitor has to pass. Test the form in a real browser
after enabling protection, since only a browser exercises the widget itself.

## Disable protection

```bash theme={null}
erdo event-pipelines update acme-leads-a1b2c3d4 --turnstile '{"enabled":false}'
```

This disables Turnstile for that pipeline while keeping passive observations and
ordinary capture enabled. Re-enabling after disabling requires the widget settings
and secret again.

## API and MCP

The same configuration object is available as the optional top-level `turnstile`
field on `PUT /v1/event-pipelines/:slug` and `erdo_update_event_pipeline`.
Omit it to preserve the current policy; pass `{"enabled":false}` to disable it.
Use the same site key, secret, and hostname fields shown above when enabling.
See [event pipeline authentication and management](/event-pipelines#managing-your-pipelines).
