Documentation

Everything you need to get protected

⚡ 60-second quick start

  1. Grab your snippet. Log in to your dashboard — the first card contains your personalised snippet with your API key already inside. Click Copy.
  2. Paste it just before the closing </body> tag on any page with a form (platform-specific instructions below).
  3. Test it. Submit your own form with [email protected] — it gets blocked, and the attempt appears in your dashboard within seconds, with the reason shown.

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.

The snippet, in depth

<script src="https://leadshield.ai/js/shield.js" data-api-key="YOUR_API_KEY" async></script>

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.

Configuration attributes

AttributeDefaultEffect
data-api-keyRequired. Your API key from the dashboard.
data-form-selectorformCSS selector limiting which forms are protected, e.g. #contact-form.
data-block-suspicioustrueSet "false" to hard-block only outright junk (score 70+) and let borderline leads through flagged.
data-badgefalseSet "true" to show a small "Protected by LeadShield" badge under the form.

Custom verdict handling

<script src="https://leadshield.ai/js/shield.js" async></script> <script> window.addEventListener('DOMContentLoaded', function() { LeadShield.init({ apiKey: 'YOUR_API_KEY', blockSuspicious: false, onValidation: function(result) { // result: { result, risk_score, reasons, suggested_domain } // Return true to allow the submission, false to block it. return result.risk_score < 80; } }); }); </script>

WordPress

One snippet protects Contact Form 7, WPForms, Gravity Forms, Elementor forms, Ninja Forms and most others automatically.

Easiest method (any theme):

  1. Install the free plugin "WPCode" (or any "insert headers and footers" plugin).
  2. Go to Code Snippets → Header & Footer → Footer box.
  3. Paste your snippet from the dashboard. Save.

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

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:

<script src="https://leadshield.ai/js/shield.js" data-api-key="YOUR_KEY" data-form-selector=".woocommerce-form-register, .wpcf7-form" async></script>

Shopify

  1. Shopify admin → Online Store → Themes → ⋯ → Edit code.
  2. Open layout/theme.liquid.
  3. Paste your snippet just before </body>. Save.

This protects storefront forms: contact pages, newsletter signups and customer registration. For headless/Hydrogen stores, call the REST API from your backend instead.

Webflow · Wix · Squarespace

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.)

Typeform, JotForm & embedded forms

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:

  1. Validate on receipt (recommended): connect the form's webhook/Zapier trigger to our REST API and discard or quarantine anything scoring 70+ before it reaches your CRM. Recipe in the Zapier section.
  2. Native embeds: JotForm's plain HTML embed (not iframe mode) renders directly in your page, and the snippet protects it like any other form.

REST API reference

Authentication

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.

Validate a lead

POST https://leadshield.ai/api/validate X-API-Key: YOUR_API_KEY Content-Type: application/json { "email": "[email protected]", // required "full_name": "Jane Smith", // optional — improves AI analysis "company_name": "Acme Ltd", // optional "phone": "+44 20 7946 0000", // optional — enables phone validation "message": "We need 50 units of…", // optional — enables AI-spam text analysis "ip": "203.0.113.9", // optional — the VISITOR's IP, see below "form_fill_time": 14.2, // optional — seconds, if you track it "form_url": "https://yoursite.com/contact" }

Field names — send what your form already uses

No need to rename anything. Every field accepts the common aliases, case-insensitively:

FieldAlso accepted
emailemail_address, emailAddress, your-email
full_namename, fullname, fullName, contact_name, your-name — or first_name + last_name
company_namecompany, companyName, organisation, organization, business, org
phonetel, telephone, mobile, phone_number, contact_number
messagecomments, comment, enquiry, inquiry, description, notes, details, your-message
ipip_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.

Response

{ "success": true, "result": "invalid", // valid | suspicious | invalid "risk_score": 95, // 0–100 "reasons": ["Disposable email domain"], "suggested_domain": null, // "gmail.com" when a provider typo is detected "usage": { "current": 412, "limit": 10000 } }

Errors

StatusMeaningFix
400Missing email in bodySend JSON with an email field
401Missing/invalid API keyCheck the X-API-Key header for typos or spaces
403Subscription inactive or trial expiredPick a plan in dashboard → billing
429Monthly validation limit reachedUpgrade, or wait for the monthly reset on the 1st

Health check

GET https://leadshield.ai/api/health → { "status": "ok" }

Webhooks PRO

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.

POST https://your-app.com/hooks/leadshield X-LeadShield-Event: lead.validated X-LeadShield-Signature: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 { "event": "lead.validated", "sent_at": "2026-07-26T09:15:04.123456Z", "result": "invalid", "risk_score": 88, "reasons": ["Disposable email domain"], "lead": { "email": "[email protected]", "full_name": "Test Lead", "company_name": "", "phone": "", "ip_address": "203.0.113.10", "form_url": "https://example.com/contact" }, "usage": { "current": 412, "limit": 10000 } }

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:

import hmac, hashlib expected = hmac.new(API_KEY.encode(), raw_body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, request.headers["X-LeadShield-Signature"]): abort(400)

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.

Ad-platform exclusion exports PRO

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:

  • Google Ads IP exclusions — a plain list of the addresses behind your blocked leads. Paste into Campaign → Settings → Additional settings → IP exclusions (Google's cap is 500, so we return the most recent).
  • Hashed email suppression — SHA-256 of each blocked address, exactly the format Google Customer Match and Meta Custom Audiences expect. Upload as an exclusion audience. Raw addresses never leave your account.

Both default to the last 90 days; add ?days=30 to narrow the window.

Bulk CSV list cleaning

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.

  • Format: a header row containing an email column (we also accept "e-mail", "email address", "work email"). A phone column is used when present. All your original columns are preserved.
  • Limits: 250 rows per upload on Starter, 1,000 on Professional, 5,000 on Enterprise. Trials get the Professional allowance. Max file size 4MB.
  • Quota: rows count as validations against your monthly allowance, but are not added to Recent activity — a one-off list clean shouldn't distort your live-traffic statistics.
  • It shares one pool with your live forms. Cleaning 9,000 rows on a 10,000 plan leaves only 1,000 for real submissions, and once the quota is gone live screening pauses until the 1st. Your dashboard shows the split (live vs list cleaning), warns at 80% and 100%, emails you at both, and refuses any upload that would take you over.

⚠️ 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.

Ad-click attribution

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.

Optional extra signals

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.

SignalWhat it catches
Datacenter / hosting IPSubmissions from AWS, Google Cloud, DigitalOcean and similar — real customers browse from consumer connections, automated traffic often doesn't
Tor exit nodeSubmissions routed through the Tor anonymity network
Country mismatchA phone number registered in one country submitted from an IP in another
Submission velocityRepeated submissions from one address in minutes, or the same email twice in quick succession
Domain ageEmail 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.

Risk thresholds & data retention ENTERPRISE

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.

Server-side examples

cURL

curl -X POST https://leadshield.ai/api/validate \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "[email protected]", "full_name": "Test User"}'

JavaScript / Node

const res = await fetch("https://leadshield.ai/api/validate", { method: "POST", headers: { "X-API-Key": process.env.LEADSHIELD_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ email: lead.email, full_name: lead.name, message: lead.message }) }); const verdict = await res.json(); if (verdict.result === "invalid") return; // drop it before the CRM

Python

import requests verdict = requests.post( "https://leadshield.ai/api/validate", headers={"X-API-Key": LEADSHIELD_KEY}, json={"email": lead["email"], "full_name": lead["name"]}, timeout=8, ).json() if verdict["result"] != "invalid": crm.create_lead(lead, score=verdict["risk_score"])

PHP

$ch = curl_init('https://leadshield.ai/api/validate'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['X-API-Key: ' . $key, 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['email' => $email]), ]); $verdict = json_decode(curl_exec($ch), true);

CRM & automation recipes

Zapier / Make (no code — works with any CRM or form tool)

  1. Trigger: your form tool's "new submission" (Typeform, JotForm, Facebook Lead Ads, anything).
  2. Action: Webhooks → POST → URL https://leadshield.ai/api/validate, header X-API-Key, JSON body with the mapped email/name/message fields.
  3. Filter: only continue if result is not invalid.
  4. Action: create the lead in your CRM, storing risk_score in a custom field.

HubSpot

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.

Salesforce

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.

Pipedrive & others

Every CRM with an API or Zapier support works with the validate-before-create pattern: score first, create second, route by risk_score.

Verdicts & risk routing

ScoreVerdictRecommended handling
0–39validStraight to sales. Fast follow-up on these is where revenue lives.
40–69suspiciousDon'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–100invalidBlocked 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.

Testing your install

  1. Submit your form with [email protected] → should be blocked ("Disposable email domain").
  2. Submit with [email protected] → "Did you mean @gmail.com?" appears.
  3. Submit with your real email, typed normally → passes instantly.
  4. Open your dashboard — all three attempts are listed with scores and reasons.

Troubleshooting

"API key required" or 401 errors

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.

Nothing appears in the dashboard

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.

A genuine lead was blocked

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.

Monthly limit reached (429)

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.

Single-page apps / AJAX forms

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.

Billing & account

  • Trial: 14 days, full Professional features, no card. When it ends, validation pauses until you pick a plan — data and API key are preserved.
  • Upgrade / downgrade / cancel: dashboard → Manage billing opens the secure Stripe portal. Changes apply immediately; cancellation runs to the end of the paid period.
  • Card statement: charges appear as AUTONOMA — LeadShield's parent company.
  • Invoices: downloadable anytime in the billing portal.
  • Data retention: screened lead data is deleted automatically after 90 days (configurable on Enterprise, down to zero).

Still stuck?

Ask the assistant in the corner — it knows everything on this page. Or contact a human; we reply within one business day.

LeadShield AssistantTypically replies in seconds
×
Hi! I know these docs inside out — ask me anything about installing, the API, or troubleshooting.