GoodInbound

Lead Screening & Spam Protection API

GoodInbound Lead Screen checks submissions from your existing forms before you route them to sales, create CRM records, or report a conversion. Use the REST API from your server, or install the JavaScript snippet on your website.

Form submission → Screening checks → Lead, review, spam, pitch, or bot
                                     ↓
                         Route using the returned decision

Choose your integration#

Integration Best for Credential
Embedded script Existing HTML, Webflow, and supported embedded forms Publishable key: pk_live_…
Server-side API Your backend decides whether to create a lead or trigger an action Secret project API key: gi_live_…
Inbound webhook Form platforms that forward completed submissions Generated webhook URL

Get the publishable key from Lead Screen → Install and a secret API key from MCP & APIs. Keep secret keys on your server. Both keys identify a single project; /leadscreen requests do not take a project ID.

Quickstart: screen a submission#

Send a non-empty fields object to POST https://goodinbound.com/api/v1/leadscreen/screens. This example runs on your server. Set GOODINBOUND_API_KEY to your project's secret key before running it.

curl --request POST 'https://goodinbound.com/api/v1/leadscreen/screens' \
  --header "Authorization: Bearer $GOODINBOUND_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: demo-request-48213' \
  --data '{
    "form": { "id": "demo-request", "name": "Demo request" },
    "external_id": "demo-request-48213",
    "fields": {
      "name": "Alex Morgan",
      "email": "alex@example.com",
      "message": "Can we book a demo for our operations team?"
    },
    "wait_ms": 800
  }'

The values above are illustrative. Use real submission data to evaluate screening. A successful response contains a saved response ID, the screening decision, and conversion eligibility. Example, shortened to the decision fields:

{
  "id": "c9ff8d11-5cb5-47aa-834c-549f91897f4c",
  "status": "screened",
  "label": "lead",
  "label_name": "Real lead",
  "reason": "Asks about a product demo",
  "confidence": 0.94,
  "action": "allow",
  "screening": { "state": "final", "revision": 1 },
  "conversion": {
    "eligible": true,
    "reason": "accepted_lead",
    "event_id": "c9ff8d11-5cb5-47aa-834c-549f91897f4c",
    "occurred_at": "2026-10-14T09:00:00.000Z"
  }
}

Request fields#

Field Required Meaning
fields Yes Object of submitted field names and values. Name, email, phone, and message fields are detected from their names and contents.
form No { "id": "stable-form-key", "name": "Readable name" }. Groups responses under an external form.
external_id No Your submission ID, up to 200 characters. Reuse it with the same form when retrying the same submission.
session No Signed Lead Screen session token from the visitor's browser. Supplies network and attribution context.
signals No Behavior data collected by LeadScreen.payload(). Forward it unchanged.
ip No Visitor IP for secret-key calls without a browser session. Obtain it from your trusted request infrastructure.
user_agent No Visitor user agent for the server-supplied network context.
wait_ms No Time to wait for the AI context check: 0–3000 ms, default 800. This is not an overall HTTP timeout.

Up to 60 normalized non-empty fields are processed, with each value truncated to 5,000 characters. Internal fields such as gi_*, _-prefixed keys, and CAPTCHA tokens are excluded. Send only the contact and message data needed for screening.

Interpret the result#

API label Meaning
lead A submission classified as a real lead
review Needs a human decision
spam Spam
junk Unwanted outreach or a sales pitch
bot Automated or abusive submission

The checks object provides results and summaries for behavior, network, identity, and message context. A check may pass, warn, fail, be skipped, or still be pending.

action is separate from label: allow lets the submission proceed, screen_out marks it for filtering, and block asks the integration to stop submission. Your check settings determine these actions. When calling the API yourself, your application must act on the response.

Do not treat HTTP 200 or action: "allow" as proof of a real lead. Use conversion.eligible for lead conversion reporting. See Lead Status Webhooks.

Pending and unavailable screening#

When screening.state is pending, the returned label is preliminary. Use the returned result_token to request the final conversion decision:

GET /api/v1/leadscreen/results/:result_token

No API key is required for this request; the token grants access to that submission's decision for 15 minutes. The response contains screening and conversion, without submitted fields or a label. Poll with backoff and stop when the state is no longer pending.

If screening cannot run, including when the screened-submission allowance is exhausted, /screens can return HTTP 200 with status: "not_screened", action: "allow", and conversion.eligible: false. Decide how your backend should handle unscreened submissions; do not report them as accepted leads.

Install the script#

Add this once to pages containing forms:

<script
  src="https://cdn.goodinbound.com/leadscreen.js"
  data-key="pk_live_YOUR_PUBLISHABLE_KEY"
  data-checks="true"
  data-wait="800"
  async
></script>

The script finds contact-style forms, collects behavior data, and screens submissions. Password and search forms are skipped. Use data-leadscreen to opt a form in or data-leadscreen-ignore to opt it out. data-checks="true" enables email checks on blur; data-wait controls the context-check wait in milliseconds, up to 3000.

The script adds gi_label, gi_reason, gi_id, and gi_session hidden fields. It stops a supported form only when the returned action is block. screen_out still lets the original form submit. Network failures let it submit unscreened.

For sensitive server actions, do not trust hidden fields as authorization: browser input can be changed. Call /screens from your server. You can collect the browser context after the script loads:

const leadScreenContext = await window.LeadScreen.payload();
// Send leadScreenContext.session and leadScreenContext.signals
// with the form data to your own backend, then forward them to /screens.

If you handle submission yourself, opt the form out of automatic interception to avoid screening twice. LeadScreen.submit(fields) screens from the browser; LeadScreen.check(fields) runs field checks. Wait for the script's load event before accessing these methods.

Custom containers can use data-leadscreen-form="demo-request" with a button marked data-leadscreen-submit. Events include leadscreen:result, leadscreen:blocked, and leadscreen:lead. Only the last is the accepted-lead conversion signal.

For CSP, allow https://cdn.goodinbound.com in script-src and https://goodinbound.com in connect-src. Cross-origin iframe forms cannot generally be intercepted. Use inbound webhooks for Typeform; HubSpot iframe callbacks may be screened after submission and cannot undo that submission.

Field checks, sessions, and webhooks#

Endpoint Purpose
POST /api/v1/leadscreen/checks Check a fields object without storing a submission or running message-context AI. Returns action, checks, email, and phone facts.
POST /api/v1/leadscreen/sessions Start a browser session with a publishable key. Send page_url, optional referrer, and a stable form string. Returns session and expires_at.
POST /api/v1/leadscreen/hooks/:token Receive completed submissions from other tools. Create the URL in Lead Screen → Install. Treat its token as a secret.

Sessions last 24 hours. Start them in the browser to capture the visitor's network; a session started on your backend observes your backend instead. Inbound webhooks accept JSON, form-encoded, and multipart payloads. Preserve gi_id when forwarding an already-screened submission to help avoid screening it twice.

Limits and errors#

Free includes 500 screened submissions per workspace per month. Pro includes 2,500, advanced AI screening, and optional monthly submission tiers. Session and field-check calls do not consume screened submissions. They have separate rate limits: 60 requests/minute per IP and 600/minute per project key budget; landing sessions use a separate budget.

The Idempotency-Key response cache lasts 24 hours. Use a stable external_id as well, and never reuse an identifier for a different submission. Pending cached decisions can be refreshed when read again.

Status Handle it by
400 Sending a non-empty fields object and valid request values
401 Checking the project key
403 Checking allowed-domain restrictions for publishable-key screening
404 on result polling Stopping polling: the token or result is unavailable or expired
429 Backing off before retrying
5xx or network failure Applying your chosen unscreened-submission policy and retrying with the same identifiers

For forms hosted by GoodInbound, use the Form Backend API; screening is already part of submission. For company and contact research after screening, use the Lead Enrichment API.