GoodInbound

Lead Screen

Lead Screen script#

Add one tag to any page with a form. The script finds contact forms (plain HTML, Webflow, HubSpot, Marketo, and forms added later), records how they're filled in, screens each one when it's submitted, and lets it through with the label in hidden fields. If Lead Screen can't be reached, the form submits as usual.

<script src="https://cdn.goodinbound.com/leadscreen.js" data-key="pk_live_..." async></script>
Property Type Required Description
data-key string Yes The project's publishable key from Settings → Lead Screen.
data-checks "true" No Check email fields when the visitor leaves them (free). Sets data-leadscreen-check on the input.
data-wait number No How long to wait for the AI context check before submitting (default 800, max 3000).

Forms with an email field or a text area are screened; login forms (with a password field) and search forms are skipped. Add data-leadscreen to a form to always screen it, or data-leadscreen-ignore to skip it. Passwords, card numbers, and files are never sent. Each submission gets hidden fields gi_label, gi_reason, gi_id, and gi_session; a form labeled block isn't submitted.

Events and forms without a <form>

<!-- A form built from divs: mark the container and the button -->
<div data-leadscreen-form="newsletter">
  <input name="email" />
  <button data-leadscreen-submit>Join</button>
</div>

<script>
  document.addEventListener("leadscreen:result", (event) => console.log(event.detail.label));
  document.addEventListener("leadscreen:blocked", (event) => showMessage(event.target));

  // Or screen yourself, e.g. before your own fetch()
  const result = await LeadScreen.submit({ email, message });
</script>

If your site sets a Content Security Policy, allow script-src https://cdn.goodinbound.com and connect-src https://goodinbound.com. Typeform embeds run in an iframe the script can't read, so use an inbound webhook for them. If a tool's webhook also forwards a form the script screened, the gi_id field stops it being counted twice.

Screen a submission#

POST /api/v1/leadscreen/screens

Screen a submission from any form and get its label while you wait. Authenticate with the project's publishable key (pk_live_…, from Settings → Lead Screen) in the browser, or a secret key (gi_live_…) from your server. Each call is stored as a response under an external form and counts as one usage.

Property Type Required Description
fields object Yes Any field names. Email, name, and message are detected automatically.
session string No A Lead Screen session token, which brings network and attribution data.
form object No { id, name } to group submissions by form. Defaults to one "Website forms" form.
external_id string No The submission's ID in your system; repeats return the first result.
ip string No Secret key only: the visitor's IP, so the network check can run.
wait_ms number No How long to wait for the AI context check (default 800, max 3000). Past it, context is pending.

Request

curl -X POST "https://goodinbound.com/api/v1/leadscreen/screens" \
  -H "Authorization: Bearer gi_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sub_48213" \
  -d '{ "fields": { "email": "priya@northwind.io", "message": "What does annual pricing look like?" } }'

Response

{
  "id": "rsp_01J9X4T7QW",
  "status": "screened",
  "label": "lead",
  "label_name": "Real lead",
  "reason": "Asks about pricing or plans",
  "confidence": 0.94,
  "action": "allow",
  "checks": {
    "behavior": { "result": "skipped", "summary": "No behavior data for this submission" },
    "network": { "result": "skipped", "summary": "No network data for this submission" },
    "identity": { "result": "pass", "summary": "Work email, active domain" },
    "context": { "result": "pass", "summary": "Asks about pricing or plans" }
  }
}

Labels are lead, review, spam, junk, and bot. action is allow, screen_out, or block, following the check actions in Settings → Lead Screen. If screening can't run, the response is status: "not_screened" with action: "allow": Lead Screen never blocks a form because of its own failure.

Field checks#

POST /api/v1/leadscreen/checks

Check fields while a visitor types, for example to warn about a disposable email. Runs the behavior, network, and identity checks only, stores nothing, and is free (rate limited).

curl -X POST "https://goodinbound.com/api/v1/leadscreen/checks" \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "fields": { "email": "asdf@mailinator.com" } }'
{ "session": null, "action": "screen_out", "checks": { "identity": { "result": "fail", "summary": "Disposable email" } } }

Sessions#

POST /api/v1/leadscreen/sessions

Start a session from the visitor's browser with the publishable key. The signed token records the visitor's network, page, UTMs, and ad click IDs. Pass it to /screens from your server so the network check still runs. Sessions are free and last 24 hours.

curl -X POST "https://goodinbound.com/api/v1/leadscreen/sessions" \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "page_url": "https://acme.com/demo?utm_source=google&gclid=Cj0K", "form": "demo-request" }'

Inbound webhooks#

POST /api/v1/leadscreen/hooks/:token

Create a webhook URL in Settings → Lead Screen and paste it into Webflow, Typeform, HubSpot workflows, Zapier, Make, or your backend. The token in the URL is the credential. JSON, form-encoded, and multipart bodies work; each tool's payload format is unwrapped automatically, and its submission ID prevents double counting. Each webhook URL is its own form in the inbox.