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.
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.
Send a test lead
Send one lead with
"test": truefrom your own server. It is validated exactly like a real lead, but never reaches outreach.cURLcurl -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.jsconst 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", ... }Pythonimport 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.
Watch it arrive, then go live
The response tells you
acceptedorrejectedright 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 goes | Value | Example |
|---|---|---|
X-Intro-Key header | Your key — authenticates the request | YOUR_KEY |
referring_partner in the body | Your Partner ID — Intro checks it against your key | YOUR_PARTNER_ID |
Where to find them
YOUR_KEYCopyYOUR_PARTNER_IDCopy- 1Open Funnel in the left menu of Intro Partners.
- 2Click Setup Integration at the top of the page.
- 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.
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Authentication | An 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. |
| Key | Your company's key — see Your credentials. |
| Transport | HTTPS with TLS 1.2 or 1.3. |
| Body | UTF-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
POST /v1/leads HTTP/1.1
Host: api.tryintro.com
Content-Type: application/json
X-Intro-Key: YOUR_KEYPayload 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.
{
"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.
Constant assigned by Intro — your Partner ID (YOUR_PARTNER_ID in the examples). Shown under Setup Integration in your Intro Partners account.
The provider that issued the certificate, lowercase — trustedform and jornaya are examples, not the whole list. Enabled by default: trustedform, jornaya.
Certificate URL or ID, exactly as issued.
Lead's first name.
Lead's last name.
E.164 format (+1…). US numbers only. Intro does not filter by area code — the state field is authoritative for geography.
Valid email address.
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 line of the lead's home address, including the unit, e.g. 120 Main St Apt 4.
City of the lead's home address.
2-letter USPS abbreviation, e.g. FL. Leads from TX and VA are refused.
5-digit US ZIP code, or ZIP+4 (33101-1234). Send it as a string so leading zeros survive (02108, not 2108).
Integer USD, no separators or currency symbol. Minimum 10,000. Anything below is refused.
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.
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.
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.
| Field | Requirement |
|---|---|
pewc_provider | Lowercase 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_token | The 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-Keyheader — it authenticates the request and is what identifies you. - As
referring_partnerin 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
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
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
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
| Status | Meaning |
|---|---|
| 200 | Received 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. |
| 403 | The 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. |
| 413 | The request body is larger than 16 KB. Send one lead per request. |
| 503 | Intro 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 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
{
"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.
| Reason | Field | Condition |
|---|---|---|
partner_mismatch | referring_partner | The 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_partner | referring_partner | The 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_provider | pewc_provider | The 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_token | pewc_token | The certificate token is absent or empty. |
missing_first_name | first_name | The field is absent or empty. |
invalid_phone_format | phone | Not a valid US number in E.164 form. |
invalid_state | state | Not one of the 50 states or DC. |
state_not_serviced | state | The state is one Intro does not service (TX, VA). |
debt_amount_not_integer | debt_amount | Not a whole number — a decimal, or formatted with separators or a currency symbol. |
debt_below_minimum | debt_amount | Below the 10,000 USD minimum. |
no_accepted_debt_type | debt_types | Empty, not an array, or no recognised value survives filtering. |
missing_last_name | last_name | The field is absent or empty. |
missing_email | email | The field is absent or empty. |
invalid_email | email | Not a valid email address. |
missing_date_of_birth | date_of_birth | The field is absent or empty. |
invalid_date_of_birth | date_of_birth | Not a real date in YYYY-MM-DD or MM/DD/YYYY, in the future, or before 1900. |
missing_street_address | street_address | The field is absent or empty. |
missing_city | city | The field is absent or empty. |
missing_zip_code | zip_code | The field is absent or empty. |
invalid_zip_code | zip_code | Not a 5-digit ZIP code or ZIP+4. |
invalid_test_flag | test | Present but not a JSON boolean. |
duplicate_lead | phone | The same phone number was already accepted — from any source — within the previous 14 days. |
| No reason matches that search. | ||
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.
{
"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.
| Control | Behaviour |
|---|---|
| AI disclosure | Intro's voice agent identifies itself as an AI assistant within the first 5 seconds of a connected call, before any qualification question. |
| Call recording | The 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 windows | Outbound 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 calls | No automated redial. Intro sends a single transactional SMS inviting the consumer to reconnect; contact resumes only if the consumer responds. |
| Opt-out | Voice or SMS opt-out keywords add the consumer to Intro's global suppression list in real time and are pushed to the partner. |
| Audio retention | Raw audio, snippets and transcripts are purged or anonymised within 30 to 60 days of the call, to NIST SP 800-88 standards. |
| Evidence retention | Consent certificates, IP records, timestamps and non-audio metadata are archived encrypted for a minimum of 5 years and are excluded from the purge above. |
| Security | TLS 1.2/1.3 in transit, AES-256 at rest, MFA and role-based access control on administrative access. |
| Incidents | A confirmed security incident affecting the partner's data is notified to the partner's security contact within 72 hours. |
| Maintenance | Scheduled 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 provides | Detail |
|---|---|
| Consent provider | Which certification provider the partner's forms use. |
| Suppression destination | An HTTPS endpoint accepting a JSON POST, or an email address for a daily file. |
| Technical contact | Name and email for integration and delivery issues. |
| Security contact | Name and email for incident notification within 72 hours. |
| Operations contact | Email for maintenance and service notices. |
| Privacy contact | Email 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.
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
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
- 1In Setup Integration, step 3 listens while the window is open.
- 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.