Phone validation in a ping-post lead-gen flow: where to call the API on each side

The problem with trusting the seller’s ping

You’re a buyer. A ping comes in carrying a phone number, a claimed line type, and an opt-in timestamp. You post, get charged, dial the number, and hit a Google Voice VoIP number. Your dialer flags it as uncontactable. Your compliance team flags it as a potential TCPA exposure. You just paid for garbage.

This happens in ping-post lead-gen because many networks validate the phone number exactly once, on the seller side, at form submit, and then trust that result forever. The fix is straightforward: validate at form submit on the seller side, attach the raw API response fields to the ping payload, and re-validate on the buyer side at post time before you accept the lead. Two API calls, two chances to catch a bad number.

Here is how to wire that up.

Seller side: validate at form submit, not at ping dispatch

The seller’s job is to call POST https://api.checkthatphone.com/v1/lookup the moment the user submits the form, before any ping goes out. Not in a background job. Not after the lead is stored. Right then, synchronously, while the user is still on the page.

A minimal request body looks like this:

{
  "phone": "6505551234",
  "litigatorFilter": true
}

Add landlineSmsLookup: true if your buyers run SMS campaigns, and pass the submitter’s ip if you have it. Every flag in the response is a string ("true" or "false"), not a JSON boolean. The response fields you want to capture and forward in the ping are:

  • dipCarrierType: the line type from a live carrier dip at lookup time. Values are mobile, landline, invalid, or undefined. This is the single most important field for most buyers.
  • nanpType: the line type from numbering-plan data (mobile, landline, or not-mobile). It doesn’t follow a number when it ports, so treat dipCarrierType as the authority when they disagree.
  • dipCarrier: the current serving carrier name.
  • dipCarrierSubType: the carrier classification. This is where you catch VoIP (IPES) and wireless resellers (WRSL), which some buyers reject outright.
  • dipPorted: "true" when the number now sits with a different carrier than the one it was originally assigned to. It tells you that a port happened, not when.
  • deliverable: "true" when the number can take an SMS and nothing else blocks it. It always agrees with action.
  • action: the API’s recommended disposition: "send", "unsubscribe", or "error" (a malformed request). Check reason for why a number got "unsubscribe".
  • litigator: only present when litigatorFilter: true is set. If this comes back "true", stop. Do not ping.
  • litigator_type and litigator_name: how the match was classified ("litigator", "plaintiff", or "agitator") and the name on record, if any.
  • ipResult: whether the IP you passed is valid ("valid-v4" or "valid-v6"). Only present when you send an ip.
  • geoState and geoSource: the state, and whether it came from the IP ("ip") or the area code ("area-code"). Comparing an IP-derived state with the state on the form is a cheap consistency check.

Do not just log these and move on. Store the full response against the lead record and include the key fields in your ping payload as structured metadata, not a freeform notes field. Buyers who have written good acceptance rules need to read these values programmatically.

If litigator is "true", kill the lead at the source. Do not ping it. The litigator scrub add-on exists precisely for this moment. If action is "unsubscribe" for another reason (a landline, a blacklist hit, a recent deactivation), decide based on what your buyers accept: a landline is useless to an SMS buyer but may be fine for a voice buyer. For more on why the litigator check matters from a TCPA liability standpoint, see the TCPA litigator scrub use case and the affiliate marketer’s TCPA exposure.

What the ping payload should carry

Different networks use different ping schemas (LeadConduit, proprietary XML, JSON over HTTP). Whatever your format, the phone validation metadata should ride alongside the lead data as first-class fields, not as a blob or an encoded string.

At minimum, buyers need to see:

phone_carrier_type: mobile
phone_nanp_type: mobile
phone_carrier: T-Mobile USA
phone_carrier_subtype: WIRELESS
phone_ported: false
phone_deliverable: true
phone_action: send
phone_litigator: false
phone_geo_state: CA
phone_geo_source: ip
phone_validated_at: 2026-10-03T14:02:11Z

If you ran landlineSmsLookup, include dipMessagingEnabled and dipMessagingProvider too. A buyer planning an SMS sequence on a number that turns out to be a landline needs to know whether it can take a text, and dipMessagingEnabled: "false" is a fast rejection signal.

A common seller mistake: they validate at ping time using a cached result from earlier in the session, or worse, from a prior lead with the same number. Numbers get ported and deactivated. A validation from 20 minutes ago is fine. One from yesterday is not. Call the API fresh at form submit, every time.

Buyer side: re-validate at post, not just read the ping fields

Here is the part most buyers skip. The ping fields tell you what the seller claims. The post is when you commit money. Before you accept, call POST https://api.checkthatphone.com/v1/lookup yourself.

Your post-time request should include the same add-ons you care about:

{
  "phone": "6505551234",
  "litigatorFilter": true,
  "landlineSmsLookup": true
}

Then compare the response against what came in on the ping. Specifically:

  • Does dipCarrierType match what the seller sent? A seller claiming mobile on a number that returns landline is a red flag.
  • Does dipCarrierSubType show IPES (VoIP) when the seller claimed a wireless line? Reject.
  • Is litigator now "true"? Our litigator data is refreshed daily, so it could have changed between ping and post, or the seller may not have run the scrub. Either way, reject.
  • Is deliverable "false"? Check reason: the number may have been deactivated in the window between ping and post.
  • Does the IP-derived geoState disagree with the state on the form? Not always a reject on its own, but a good signal to combine with other flags.

Your acceptance rules should be explicit. Something like: accept only if dipCarrierType is "mobile", dipCarrierSubType is not "IPES", litigator is "false", and action is "send". Everything else goes to review or auto-reject. Write these as code, not as a checklist in a wiki. The sales lead validation use case has more detail on building these filters, and how lead buyers score affiliate phone leads covers the scoring side.

The buyer-side call adds a synchronous round-trip to your post handling. Measure it from your own servers and budget for it within your network’s post response deadline. If the deadline is too tight, accept the post provisionally and run the check asynchronously before the number reaches your dialer.

Handling bulk historical lists

The ping-post flow above covers real-time lead traffic. If you’re a buyer auditing a batch of leads you already received, or a seller cleaning a list before activating a campaign, the single-number /v1/lookup endpoint is the wrong tool. Calling it in a loop at volume is slow, fragile, and will hit rate limits.

For that, use the bulk CSV verification in the CheckThatPhone dashboard (Bulk CSV). Upload your file, select the add-ons you want (litigator scrub, landline SMS lookup), and download the result file. It deduplicates automatically, runs carrier and line-type validation on every unique number, and the upload and result file are hard-deleted within 24 hours. This is the right path for contact list hygiene work. See contact list hygiene for more on that workflow.

A practical rejection threshold

If you’re a buyer building your acceptance rules from scratch, here is a reasonable starting point:

  • Hard reject: litigator: "true", action: "error", dipCarrierType: "invalid", dipCarrierSubType: "IPES"
  • Soft reject / review: action: "unsubscribe" for a reason other than a litigator match, dipPorted: "true" combined with an IP-derived geoState that doesn’t match the form, dipCarrierType: "undefined"
  • Accept: dipCarrierType: "mobile", litigator: "false", action: "send"

Adjust based on your vertical. A home services buyer might accept landlines. An insurance SMS campaign cannot.

The affiliate and lead-gen use case shows the same flow from the network’s side. The full field reference is in the API docs, and the pricing page has current costs for the litigator add-on and landline SMS lookup if you’re modeling unit economics on a per-lead basis.

Stop trusting the ping. Validate it yourself.

Run your own numbers through it

Carrier, line type, porting and deliverability for US and Canadian numbers in one call. The Free plan includes 500 credits a month.