Intro Docs

Credit Card Debt Relief Lead Webhook Integration

On each form submission, the partner fires a real-time HTTP webhook to Intro carrying the lead's contact data, qualification data, and proof of prior express written consent. Intro validates the lead synchronously and answers accepted or rejected in the same HTTP response, usually within a few seconds — so give your client a 10-second timeout. Only accepted leads enter Intro's CRM and outreach.

The rules on this page are Intro's standard rules. If your account differs on any of them, the setup message from your team lists exactly what is different.

Quickstart

Three steps from zero to your first accepted lead.

  1. Find your credentials

    You need two values — your key and your Partner ID, which are the same string. They're in the setup message from your team, and under Setup Integration in your Intro Partners account.

  2. Send a test lead

    Send one lead with "test": true from your own server. It is validated exactly like a real lead, but never reaches outreach.

    cURL
    curl -X POST https://api.tryintro.com/v1/leads \
      --max-time 10 \
      -H "Content-Type: application/json" \
      -H "X-Intro-Key: YOUR_KEY" \
      -d '{
        "referring_partner": "YOUR_PARTNER_ID",
        "pewc_provider": "trustedform",
        "pewc_token": "https://cert.trustedform.com/a1b2c3d4e5f6",
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+13055551234",
        "email": "john.doe@example.com",
        "date_of_birth": "1985-04-12",
        "street_address": "120 Main St Apt 4",
        "city": "Miami",
        "state": "FL",
        "zip_code": "33101",
        "debt_amount": 25000,
        "debt_types": ["credit_card"],
        "test": true
      }'
    Node.js
    const res = await fetch("https://api.tryintro.com/v1/leads", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Intro-Key": process.env.INTRO_KEY, // YOUR_KEY — keep it on the server
      },
      body: JSON.stringify({
        referring_partner: "YOUR_PARTNER_ID",
        pewc_provider: "trustedform",
        pewc_token: "https://cert.trustedform.com/a1b2c3d4e5f6",
        first_name: "John",
        last_name: "Doe",
        phone: "+13055551234",
        email: "john.doe@example.com",
        date_of_birth: "1985-04-12",
        street_address: "120 Main St Apt 4",
        city: "Miami",
        state: "FL",
        zip_code: "33101",
        debt_amount: 25000,
        debt_types: ["credit_card"],
        test: true,
      }),
      signal: AbortSignal.timeout(10_000),
    });
    
    const verdict = await res.json(); // { status: "accepted" | "rejected", ... }
    Python
    import os
    import requests
    
    res = requests.post(
        "https://api.tryintro.com/v1/leads",
        headers={"X-Intro-Key": os.environ["INTRO_KEY"]},  # YOUR_KEY
        json={
            "referring_partner": "YOUR_PARTNER_ID",
            "pewc_provider": "trustedform",
            "pewc_token": "https://cert.trustedform.com/a1b2c3d4e5f6",
            "first_name": "John",
            "last_name": "Doe",
            "phone": "+13055551234",
            "email": "john.doe@example.com",
            "date_of_birth": "1985-04-12",
            "street_address": "120 Main St Apt 4",
            "city": "Miami",
            "state": "FL",
            "zip_code": "33101",
            "debt_amount": 25000,
            "debt_types": ["credit_card"],
            "test": True,
        },
        timeout=10,
    )
    
    verdict = res.json()  # {"status": "accepted" | "rejected", ...}

    Set your client timeout at 10 seconds. The three samples above already do. A 3- or 5-second timeout is too short: it cuts off answers Intro is still deciding.

  3. Watch it arrive, then go live

    The response tells you accepted or rejected right away, and the request shows up under Setup Integration within seconds. Once a test is accepted, send real leads without the flag.

Your credentials

Intro assigns your company one value. It is your API key and your Partner ID, and every request carries it twice: in the X-Intro-Key header and as referring_partner in the body.

Where it goesValueExample
X-Intro-Key headerYour key — authenticates the requestYOUR_KEY
referring_partner in the bodyYour Partner ID — Intro checks it against your keyYOUR_PARTNER_ID

Where to find them

FunnelSetup IntegrationUpload Leads
Setup Integration
2Use these values
X-Intro-KeyYOUR_KEYCopy
referring_partnerYOUR_PARTNER_IDCopy
  1. 1Open Funnel in the left menu of Intro Partners.
  2. 2Click Setup Integration at the top of the page.
  3. 3Step 2, Use these values, shows your key and Partner ID, each with a copy button.

Don't have access to Intro Partners yet? Ask your team for the setup message. It includes an invite link to join their account, the values above, and the way to Setup Integration, where your tests show up.

Endpoint & request

All partners post to the same endpoint. Leads are attributed and routed by your partner identity value.

POSThttps://api.tryintro.com/v1/leadsPost a lead
POSThttps://api.tryintro.com/v1/pingOptional pre-ping
PropertyValue
MethodPOST
Content-Typeapplication/json
AuthenticationAn X-Intro-Key header carrying your key. The key is yours alone — it identifies you on every request and is not shared with other partners.
KeyYour company's key — see Your credentials.
TransportHTTPS with TLS 1.2 or 1.3.
BodyUTF-8 JSON, one lead per request.

Keep the key on your server. Send leads from your own backend or form platform — never from a web page, a browser script, or anything a visitor can read. Treat the key as confidential.

Example request headers

Request
POST /v1/leads HTTP/1.1
Host: api.tryintro.com
Content-Type: application/json
X-Intro-Key: YOUR_KEY

Payload schema

The complete JSON body expected on each submission. All fields sit at the top level — there are no nested objects. Fields Intro does not recognise are ignored, but stored with the lead.

POST /v1/leads · body
{
  "referring_partner": "YOUR_PARTNER_ID",

  "pewc_provider": "trustedform",
  "pewc_token": "https://cert.trustedform.com/a1b2c3d4e5f6",

  "first_name": "John",
  "last_name": "Doe",
  "phone": "+13055551234",
  "email": "john.doe@example.com",
  "date_of_birth": "1985-04-12",

  "street_address": "120 Main St Apt 4",
  "city": "Miami",
  "state": "FL",
  "zip_code": "33101",

  "debt_amount": 25000,
  "debt_types": ["credit_card"],

  "test": false
}

debt_amount is an integer in USD with a minimum of 10,000, and debt_types accepts credit_card, unsecured_personal_loan, medical_collections.

Field reference

Send fields as typed below — debt_amount as a number, debt_types as an array. Every field is required except test and ping_id: the lead's full name, phone, email, date of birth and street address, together with its debt, are what let Intro work the lead.

Send every required field on every lead. Today a lead that is missing last_name, email, date_of_birth, street_address, city or zip_code is still accepted, and the response lists what was missing or malformed (see The response). Intro will reject incomplete leads later, with the reasons listed under Rejection reasons — build your integration to send all of them now.

referring_partnerstringrequired

Constant assigned by Intro — your Partner ID (YOUR_PARTNER_ID in the examples). Shown under Setup Integration in your Intro Partners account.

pewc_providerstringrequired

The provider that issued the certificate, lowercase — trustedform and jornaya are examples, not the whole list. Enabled by default: trustedform, jornaya.

pewc_tokenstringrequired

Certificate URL or ID, exactly as issued.

first_namestringrequired

Lead's first name.

last_namestringrequired

Lead's last name.

phonestringrequired

E.164 format (+1…). US numbers only. Intro does not filter by area code — the state field is authoritative for geography.

emailstringrequired

Valid email address.

date_of_birthstringrequired

YYYY-MM-DD, e.g. 1985-04-12. MM/DD/YYYY is also understood. Must be a real calendar date, not in the future and not before 1900.

street_addressstringrequired

Street line of the lead's home address, including the unit, e.g. 120 Main St Apt 4.

citystringrequired

City of the lead's home address.

statestringrequired

2-letter USPS abbreviation, e.g. FL. Leads from TX and VA are refused.

zip_codestringrequired

5-digit US ZIP code, or ZIP+4 (33101-1234). Send it as a string so leading zeros survive (02108, not 2108).

debt_amountintegerrequired

Integer USD, no separators or currency symbol. Minimum 10,000. Anything below is refused.

debt_typesstring[]required

Any combination of credit_card, unsecured_personal_loan, medical_collections, with at least one accepted value. Always send as an array, even for a single value. Values outside this list are discarded — if none survives, the lead is refused.

testbooleanoptional

true validates the lead and answers without sending it to outreach. Omit it, or send false, for real leads. Any other value — including the string "true" — is refused.

ping_idstringoptional

The ID returned by a prior call to the ping endpoint. Only relevant in the two-step ping/post flow. Ignored otherwise.

Consent certificate (PEWC)

Every submission must carry proof of the consumer's prior express written consent. Intro does not accept leads without it.

FieldRequirement
pewc_providerLowercase identifier of the provider that issued the certificate. trustedform and jornaya are examples — they are not the whole list, and another provider can be enabled for you. Enabled by default: trustedform, jornaya. Tell us which one your forms use during onboarding and we will confirm it before you go live.
pewc_tokenThe certificate URL or ID exactly as issued by the provider. Send the full string — do not truncate, shorten, re-encode, or wrap it in your own redirect.
  • The certificate must be claimable at the moment Intro ingests the lead. Fire the webhook in real time so the certificate is still retrievable.
  • Intro stores and forwards the token as received. Intro performs no substantive audit of the certificate's contents; the validity of the underlying consent remains the Publisher's responsibility.
  • One certificate per lead. Do not reuse a token across submissions.

A submission that arrives with an empty or missing pewc_token is rejected on the spot. The lead is not contacted, not worked, and not billable.

Partner identity

Your Partner ID and your API key are the same value — one string, assigned to your company, in the setup message from your team, and under Setup Integration in your Intro Partners account. Send it in two places on every request:

  • In the X-Intro-Key header — it authenticates the request and is what identifies you.
  • As referring_partner in the body — Intro checks it against your key.

Send both values character-for-character as provided. If referring_partner does not match the partner your key belongs to, the lead is rejected as partner_mismatch rather than silently misrouted.

  • Hard-code both values. Do not derive them from form input, and never let an end user populate them.
  • Keep the key server-side. If it is ever exposed, tell us and we reissue it — only your traffic is affected.

The response

Intro answers every authenticated request synchronously, with the decision already made. There is no second, asynchronous verdict: what you get back is final.

Set your client timeout at 10 seconds. Most answers come back within a few seconds, and some take longer; a 3- or 5-second timeout cuts off requests Intro is still deciding. Intro keeps processing a request after your client hangs up, so a lead you gave up on may already be accepted, and resending it returns duplicate_lead.

Accepted

200 · accepted
HTTP/1.1 200 OK

{
  "status": "accepted",
  "lead_id": "7a179944-3471-4e40-8d19-cae9d8af5e6a",
  "received_at": "2026-09-14T22:18:13.006Z"
}

Store lead_id. It is Intro's identifier for that lead and the reference for any later reconciliation.

Accepted, with fields missing

200 · accepted, incomplete
HTTP/1.1 200 OK

{
  "status": "accepted",
  "lead_id": "7a179944-3471-4e40-8d19-cae9d8af5e6a",
  "received_at": "2026-09-14T22:18:13.006Z",
  "missing_fields": ["date_of_birth", "zip_code"],
  "invalid_fields": ["email"]
}

When a required field was absent (missing_fields) or malformed (invalid_fields), the lead is still accepted today and the response names those fields. Each list appears only when it has something in it, so a complete lead gets the response above. Fix your mapping as soon as you see them: incomplete leads will be rejected later. A test lead reports them the same way.

Rejected

200 · rejected
HTTP/1.1 200 OK

{
  "status": "rejected",
  "reason": "debt_below_minimum",
  "field": "debt_amount"
}

A rejection is returned as HTTP 200, not 4xx. A rejected lead is a business decision, not a protocol failure — the request was received and understood. Never retry a rejection. Retrying will return the same verdict and, for duplicate_lead, will keep returning it for 14 days.

A body that is not parseable JSON also comes back as 200 with reason: "malformed_json", so you never have to special-case a parse error into a different code path. Validation stops at the first rule that fails, so reason reports one problem at a time; field names the offending field, or is null when the problem is the payload as a whole.

HTTP status codes

StatusMeaning
200Received and decided. Read status in the body to know whether the lead was accepted or rejected. On a test ("test": true), the lead was valid and was not sent to outreach.
403The X-Intro-Key header is missing, unknown, or belongs to a disabled account. Nothing was processed. Fix the header or contact us — do not retry as-is.
413The request body is larger than 16 KB. Send one lead per request.
503Intro is temporarily unreachable or could not process the request. Retry with exponential backoff, up to 3 times.

Retry only what a retry can fix: timeouts and 503. Never retry a 200, a 403 or a 413. If a retry after a timeout comes back duplicate_lead, the first attempt was most likely accepted. Do not batch, queue, or send scheduled digests — each submission is its own request, fired in real time within seconds of the form submit.

Two-step ping/post

If your platform runs a classic ping/post flow — ask first, deliver second — Intro exposes a dedicated ping endpoint. You do not need it: a single post to the lead endpoint already returns the same verdict synchronously, and most partners use only that.

Use the ping when you want to know whether Intro will take a lead before you transmit the consumer's personal data, before you decide which buyer to route it to, or to avoid serving media against a lead Intro would decline.

Ping request

POST /v1/ping
POST https://api.tryintro.com/v1/ping
X-Intro-Key: YOUR_KEY

{
  "referring_partner": "YOUR_PARTNER_ID",
  "phone": "+13055551234",
  "state": "FL",
  "debt_amount": 25000,
  "debt_types": ["credit_card"]
}

No pewc_token, and none of the consumer's personal data — no name, email, date of birth or address. The ping checks partner identity, phone format, state, debt amount, debt types, and the duplicate window. All five fields above are evaluated: a ping that omits debt_amount or state is rejected on that field, not treated as unknown. Give the ping the same 10-second client timeout as the post.

Ping response

200 · ping accepted
{
  "status": "accepted",
  "ping_id": "a078caaa-dd9d-449a-8637-46f9f1e7084e",
  "received_at": "2026-09-14T23:06:54.025Z",
  "expires_at": "2026-09-14T23:36:54.424Z"
}

Then send the full lead to the lead endpoint as usual, adding ping_id so the two calls are linked in Intro's records. A rejection uses the same shape as the post — status, reason, field — drawn from the rejection reasons, minus the consent and personal-data reasons the ping does not evaluate.

The ping is indicative, not a reservation. It does not hold the phone number for you, and an accepted ping is not a commitment to buy. The post is the authoritative decision — it re-runs every rule, including the duplicate window, which may have closed in between. Post promptly, ideally within the same minute.

Rejection reasons

The complete list. Every rejection returns exactly one of these in reason, with the matching field.

ReasonFieldCondition
partner_mismatchreferring_partnerThe referring_partner sent does not match the partner your key belongs to.
partner_paused—Your account is paused. Traffic is not being accepted right now; contact us before resuming.
partner_cap_reached—You have hit the daily volume cap agreed for your account. The cap resets at midnight UTC.
trial_quota_reached—You have sent the leads included in your trial. Our team is evaluating their performance and will contact you.
monthly_cap_reached—You have reached the monthly lead limit for your account. The limit resets on the 1st of the month, UTC.
missing_referring_partnerreferring_partnerThe field is absent while Intro cannot resolve you from your key alone. Rare; send the field on every request and you will never see it.
malformed_json—The request body is not parseable JSON.
invalid_pewc_providerpewc_providerThe provider is missing, or is not one of the ones enabled for your account (by default trustedform, jornaya). Ask us to enable another.
missing_pewc_tokenpewc_tokenThe certificate token is absent or empty.
missing_first_namefirst_nameThe field is absent or empty.
invalid_phone_formatphoneNot a valid US number in E.164 form.
invalid_statestateNot one of the 50 states or DC.
state_not_servicedstateThe state is one Intro does not service (TX, VA).
debt_amount_not_integerdebt_amountNot a whole number — a decimal, or formatted with separators or a currency symbol.
debt_below_minimumdebt_amountBelow the 10,000 USD minimum.
no_accepted_debt_typedebt_typesEmpty, not an array, or no recognised value survives filtering.
missing_last_namelast_nameThe field is absent or empty.
missing_emailemailThe field is absent or empty.
invalid_emailemailNot a valid email address.
missing_date_of_birthdate_of_birthThe field is absent or empty.
invalid_date_of_birthdate_of_birthNot a real date in YYYY-MM-DD or MM/DD/YYYY, in the future, or before 1900.
missing_street_addressstreet_addressThe field is absent or empty.
missing_citycityThe field is absent or empty.
missing_zip_codezip_codeThe field is absent or empty.
invalid_zip_codezip_codeNot a 5-digit ZIP code or ZIP+4.
invalid_test_flagtestPresent but not a JSON boolean.
duplicate_leadphoneThe same phone number was already accepted — from any source — within the previous 14 days.

If you ever receive internal_validation_error, that is a fault on Intro's side, not in your payload. Report it to your technical contact; do not change your integration.

Duplicates, acceptance & billability

Intro deduplicates on phone number across all sources, not just yours. A number accepted from any partner within the previous 14 calendar days is rejected as duplicate_lead. Test submissions never count toward this window and never create one.

An accepted lead is a worked lead: it enters Intro's CRM and outreach immediately. Because the verdict is synchronous and final, accepted leads are the billable set — there is no later reconciliation that removes leads from it. Rejected leads are not contacted, not worked, and not billable, and you know which ones they are the moment you send them.

Suppression & consumer requests

When a consumer opts out with Intro by voice or SMS, Intro pushes the suppression to the partner within 24 hours. The partner supplies a destination during onboarding — an HTTPS endpoint that accepts a JSON POST, or an email address for a daily file.

Suppression · POST to your endpoint
{
  "referring_partner": "YOUR_PARTNER_ID",
  "phone": "+13055551234",
  "opted_out_at": "2026-09-15T18:04:00Z"
}

The partner applies these suppressions in its own systems and does not re-submit a suppressed consumer. Consumer privacy requests (access, correction or deletion) that reach the partner and concern a lead sent to Intro must be forwarded to Intro's privacy contact. Intro deletes or anonymises the consumer's personal data in its operating systems within three business days. Consent certificates and non-audio evidence are retained regardless, as required for legal defence.

How Intro handles the lead

The partner does not configure any of the following. It is stated so the partner knows what happens to a lead after delivery and can answer consumer questions.

ControlBehaviour
AI disclosureIntro's voice agent identifies itself as an AI assistant within the first 5 seconds of a connected call, before any qualification question.
Call recordingThe consumer is notified at the start of the call; continuing the call is treated as consent. Recording and transcription stop immediately on verbal objection.
Contact windowsOutbound calls and SMS run only within the legal local window for the consumer's time zone, derived from area code or ZIP. Anything outside is queued for the next valid window. Daily contact-frequency caps apply.
Dropped callsNo automated redial. Intro sends a single transactional SMS inviting the consumer to reconnect; contact resumes only if the consumer responds.
Opt-outVoice or SMS opt-out keywords add the consumer to Intro's global suppression list in real time and are pushed to the partner.
Audio retentionRaw audio, snippets and transcripts are purged or anonymised within 30 to 60 days of the call, to NIST SP 800-88 standards.
Evidence retentionConsent certificates, IP records, timestamps and non-audio metadata are archived encrypted for a minimum of 5 years and are excluded from the purge above.
SecurityTLS 1.2/1.3 in transit, AES-256 at rest, MFA and role-based access control on administrative access.
IncidentsA confirmed security incident affecting the partner's data is notified to the partner's security contact within 72 hours.
MaintenanceScheduled maintenance is announced by email at least 24 hours in advance, outside peak hours.

Onboarding checklist

Intro provides your key and your referring_partner ID — in the setup message from your team, and under Setup Integration in your Intro Partners account. The endpoint URLs are in this guide.

The partner providesDetail
Consent providerWhich certification provider the partner's forms use.
Suppression destinationAn HTTPS endpoint accepting a JSON POST, or an email address for a daily file.
Technical contactName and email for integration and delivery issues.
Security contactName and email for incident notification within 72 hours.
Operations contactEmail for maintenance and service notices.
Privacy contactEmail for consumer privacy requests in either direction.

Go-live check

Before sending real leads, send one test: the same request with "test": true, your partner ID, a live consent certificate and a phone number you control. Intro validates it exactly like a real lead — same rules, same rejection reasons — and answers without sending it to outreach and without counting it toward the duplicate window.

200 · test accepted
HTTP/1.1 200 OK

{
  "status": "accepted",
  "test": true,
  "lead_id": "4c6604d3-4127-4bab-bb95-1243f9ea3169",
  "received_at": "2026-09-14T22:18:17.171Z"
}

"status": "accepted" on the test, with no missing_fields or invalid_fields in the response, is your go-live: from then on, send real leads without the flag. A "status": "rejected" tells you exactly which rule to fix before you go live — that is the point of the test. You can send as many tests as you need while building; use them to walk your integration through each rejection reason and confirm your error handling.

The answer is in the response — you do not have to ask anyone. Every request is decided while it is open, so your own logs are the record of what happened. Your tests also show up under Setup Integration in your Intro Partners account within seconds, each refusal with its reason — until one is accepted, which confirms your integration.

Watch your tests arrive

Setup Integration
3Send a test and watch it arriveLive

The consent certificate was missing. · 1:32 PM

Send `pewc_token` exactly as the provider issued it, on every lead.

Earlier tests

  • That isn't a valid US phone number. · 1:28 PM
  • We don't service that state (TX and VA). · 1:28 PM
  1. 1In Setup Integration, step 3 listens while the window is open.
  2. 2Each test your systems send appears within seconds, refused with the reason and what to change — until one is accepted, which confirms your integration.

Need a hand?

The fastest way is a call — we walk through your integration and answer questions live.

If you would rather write, copy all three of us in and whoever is free picks it up: vicente@tryintro.com · barry@tryintro.com · ben@tryintro.com. Email is the slowest of the three — your account and the call both answer sooner.