That's the whole install. The snippet automatically finds every form on the page (including ones added later by page builders), and protects them from bots, disposable emails, typos and AI-generated spam.
What it does on every protected form: injects an invisible honeypot field, measures fill-time and human interaction (keystrokes, mouse, touch — so mobile users and autofill are never penalised), computes a privacy-safe device fingerprint hashed in the browser, and validates on submit. Fail-open guarantee: if our API is slow (>5s) or unreachable, the form submits normally — our outage can never lose you a lead.
| Attribute | Default | Effect |
|---|---|---|
| data-api-key | — | Required. Your API key from the dashboard. |
| data-form-selector | form | CSS selector limiting which forms are protected, e.g. #contact-form. |
| data-block-suspicious | true | Set "false" to hard-block only outright junk (score 70+) and let borderline leads through flagged. |
| data-badge | false | Set "true" to show a small "Protected by LeadShield" badge under the form. |
One snippet protects Contact Form 7, WPForms, Gravity Forms, Elementor forms, Ninja Forms and most others automatically.
Easiest method (any theme):
Without a plugin: Appearance → Theme File Editor → footer.php → paste just before </body>. (Use a child theme so updates don't remove it.)
Verify: submit any form with [email protected] → blocked → visible in your dashboard.
WooCommerce runs on WordPress, so the WordPress install above covers it — the snippet protects account-registration and contact forms storewide.
Recommended scope for stores: protect registration and enquiry forms, but leave the checkout form alone (payment friction is sacred). Limit scope with:
This protects storefront forms: contact pages, newsletter signups and customer registration. For headless/Hydrogen stores, call the REST API from your backend instead.
Webflow: Project settings → Custom code → Footer code → paste snippet → Save & publish.
Wix: Settings → Custom code → + Add custom code → paste snippet → set "Place code in: Body — end" → apply to all pages.
Squarespace: Settings → Advanced → Code injection → paste into the Footer box → Save. (Business plan or higher required by Squarespace.)
Form builders that render inside an iframe (Typeform, JotForm, Google Forms embeds) can't be reached by a snippet on the host page — the browser isolates iframes. Two good options:
Every request needs your API key in the X-API-Key header. Requests without a valid key get 401 Unauthorized. Manage or regenerate your key in the dashboard — regeneration kills the old key instantly.
No need to rename anything. Every field accepts the common aliases, case-insensitively:
| Field | Also accepted |
|---|---|
| email_address, emailAddress, your-email | |
| full_name | name, fullname, fullName, contact_name, your-name — or first_name + last_name |
| company_name | company, companyName, organisation, organization, business, org |
| phone | tel, telephone, mobile, phone_number, contact_number |
| message | comments, comment, enquiry, inquiry, description, notes, details, your-message |
| ip | ip_address, client_ip, visitor_ip |
Only email is required. Send whatever else your form happens to collect — most forms only ask for a name, email and message, and that's perfectly normal. A lead is never penalised for a field your form doesn't ask for.
⚠️ Calling from your server, CRM or Zapier? Send the ip field. In a server-to-server call the connecting IP is your server, not the visitor's — so pass the visitor's real IP in the body, or the IP-reputation layer will check the wrong address. The JavaScript snippet handles this for you automatically.
| Status | Meaning | Fix |
|---|---|---|
| 400 | Missing email in body | Send JSON with an email field |
| 401 | Missing/invalid API key | Check the X-API-Key header for typos or spaces |
| 403 | Subscription inactive or trial expired | Pick a plan in dashboard → billing |
| 429 | Monthly validation limit reached | Upgrade, or wait for the monthly reset on the 1st |
Set a webhook URL in your dashboard and we'll POST every verdict to it as it happens — no polling. Choose every lead (push clean ones straight into your CRM) or blocked only (route junk to a review queue). Use the Send test button to confirm your endpoint before going live.
Verify it came from us. X-LeadShield-Signature is an HMAC-SHA256 of the raw request body, keyed with your API key — the same scheme Stripe uses. Compare it against your own computation and reject anything that doesn't match:
Reply with any 2xx status. We retry once on failure and then move on — webhooks are notifications, so a dead endpoint never delays or changes a validation.
Blocking junk at your form protects your CRM, but your ad platform still counts those clicks as conversions and keeps hunting for more of the same. Two downloads in your dashboard close that loop:
Both default to the last 90 days; add ?days=30 to narrow the window.
No account yet? The free lead audit runs the same deterministic checks on up to 250 rows with no signup and no card — useful for a first look before you install anything.
Screen a list you already have — a CRM export, an old spreadsheet, an inherited database — without waiting for new form traffic. Dashboard → Clean a list you already have → choose your .csv → the annotated file downloads with three columns added: verdict, risk score and reasons.
⚠️ Bulk results are deliberately conservative. A CSV row has no browser session and no visitor IP, so bulk cleaning runs the deterministic layers only — email syntax, disposable domains, live MX, typo detection, phone format and in-file duplicates. Behavioural signals, IP reputation and AI contextual analysis need a live form submission. Anything flagged here is genuinely junk, but a row that looks clean can still be caught in real time.
The snippet reads gclid, gbraid, wbraid, fbclid, msclkid and UTM parameters from the landing-page URL (or from a matching hidden form field, if your site already captures them) and sends them with the lead. That's what powers the wasted ad spend figure on your dashboard — it separates junk that cost you ad money from junk that arrived organically.
No cookies or browser storage are used, in line with our privacy promise. The trade-off is that a click ID is only visible when the form is reached during the same visit as the ad click — which covers the usual PPC landing-page flow. Sending server-side? Include the click ID in your /api/validate body and it is stored the same way.
Beyond the standard layers, five further signals can be switched on for your account. They are off by default — we run them in observation mode first, measure what they would have caught on your own traffic, and only enable one when the evidence supports it. Nothing changes on your account without that review.
| Signal | What it catches |
|---|---|
| Datacenter / hosting IP | Submissions from AWS, Google Cloud, DigitalOcean and similar — real customers browse from consumer connections, automated traffic often doesn't |
| Tor exit node | Submissions routed through the Tor anonymity network |
| Country mismatch | A phone number registered in one country submitted from an IP in another |
| Submission velocity | Repeated submissions from one address in minutes, or the same email twice in quick succession |
| Domain age | Email domains registered within the last 30 days, a common disposable-infrastructure pattern |
When a signal is enabled it behaves like every other layer: it adds to the 0–100 risk score and its reason appears on the lead, so you always see exactly why something was flagged. Ask support if you'd like any of them turned on for your account.
Enterprise accounts can move where verdicts fall on the 0–100 score (defaults: invalid at 70, suspicious at 40) — lower the cut-off to block harder, raise it to let borderline leads through. You can also set how long screened lead details are kept before automatic deletion; 0 clears them at the next nightly sweep, so aggregate scores and counts survive but the personal data does not. Both live in dashboard → Advanced controls.
Use a Workflow with a custom-code action (or the Zapier recipe above): on contact creation, POST the contact's email/name to /api/validate, write risk_score to a custom property, and branch — 70+ gets a "junk" lifecycle stage and never reaches a sales queue.
An Apex trigger (before insert on Lead) calling our REST endpoint does the same: store the score in a custom field and route assignment rules on it. Any Salesforce developer can wire this in under an hour with the reference above.
Every CRM with an API or Zapier support works with the validate-before-create pattern: score first, create second, route by risk_score.
| Score | Verdict | Recommended handling |
|---|---|---|
| 0–39 | valid | Straight to sales. Fast follow-up on these is where revenue lives. |
| 40–69 | suspicious | Don't call — nurture. Email sequence first; let engagement prove intent. (Blocked by the snippet by default; set data-block-suspicious="false" to let them through flagged.) |
| 70–100 | invalid | Blocked at the form / never enters the CRM. Reasons are always attached. |
Typo rescue: when a visitor mistypes a major provider (gmial.com), the response includes suggested_domain and the snippet shows "Did you mean @gmail.com?" — recovering a customer you'd otherwise lose.
The key must be sent exactly as issued — check for leading/trailing spaces, and that it's in the data-api-key attribute (snippet) or X-API-Key header (API). Still stuck? Regenerate the key in your dashboard and update your site — old keys stop working immediately.
1) View your page source and confirm the snippet is present before </body> (some cache/optimizer plugins strip scripts — whitelist shield.js). 2) Confirm your form has an email field — forms without one are ignored. 3) Check the browser console for a LeadShield error message.
Check the reason in your dashboard first — it's usually a disposable domain or honeypot hit, which are near-certain junk. If the verdict was "suspicious" (40–69), set data-block-suspicious="false" so only outright junk is hard-blocked, or take full control with onValidation. Believe the engine got it flat wrong? Send us the example — we tune on real cases and reply fast.
Validation pauses (forms keep working, unvalidated) until the 1st — or resumes the moment you upgrade in dashboard → Manage billing. We email you at 80% and 100% so it never surprises you.
The snippet watches the page for new forms automatically (MutationObserver), so SPAs and dynamically-injected forms are covered. If your form submits via your own JavaScript rather than a real submit event, call the REST API from your handler instead.
Ask the assistant in the corner — it knows everything on this page. Or contact a human; we reply within one business day.