Inbound Webhooks (Buyer Payouts)

Receive real payout amounts from external buyers and networks (Ringba, MarketCall, Trackdrive) so each call's revenue reflects what you actually earned.

Overview

Inbound webhooks let external buyers and networks report the dollar amount they paid you for each call. CallScaler matches the report to the original call and stamps the buyer-reported payout as the call's true revenue — no more estimating from static buyer-set rates.

Who this is for:
• You sell calls to a buyer who pays per qualified call (Ringba, MarketCall, Trackdrive, or any custom buyer system).
• You run a Pay Per Call network and need real buyer payouts feeding into your publisher rev-share calculations.

Looking for the other direction — sending call data from CallScaler to your CRM or Zapier after each call? See Post-Call Webhooks.

Setup

1. Open Settings → API & Webhooks → Webhooks.
2. Click New webhook and give it a name (e.g. "Ringba — HVAC Buyer").
3. Optionally pick the buyer from the dropdown to scope events to that buyer's reporting.
4. Save. CallScaler generates a unique URL — copy it and paste it into the buyer's postback / pixel / webhook setting on their side.

One webhook per source. Use a separate webhook for each buyer or network so you can pause, audit, and rotate secrets independently.

How Matching Works

When a buyer POSTs to your webhook URL, CallScaler tries to match the payload to one of your calls in this order — first hit wins:

1. ref_id — If the buyer echoes back our outbound ping ref ID, we match exactly. This is the most reliable strategy.
2. call_id — If the buyer echoes back our internal call UUID, we look it up directly.
3. caller + duration window — Fallback heuristic: same caller number, duration within ±60 seconds (the buyer's leg is typically ~30s shorter than ours due to ring/IVR), within a configurable time window (default 24h).

If nothing matches, the event is logged as unmatched with a reason — visible in the webhook's Recent events panel so you can debug field mismatches.

Field names are auto-detected for common sources (Ringba, MarketCall, Arroyo, Trackdrive). You only need to override field names if your buyer uses an unusual schema.

Recognized Field Names

CallScaler accepts query string, form-encoded, or JSON bodies. Field names are case-insensitive. For each of these slots, the configured field name wins, then a comprehensive alias list:

  • ref_idref_id, external_id, external_ref, click_id, pub_id, subid, tid, transaction_id
  • call_idcall_id, callid, reference_id, our_call_id, internal_id
  • callercaller_id, caller, callerid, from, from_number, phone, caller_phone, caller_number, ani
  • durationduration, duration_seconds, talk_duration, talk_time, call_duration, length, seconds
  • payoutpayout, payout_or_blank, revenue, amount, value, bid, bid_amount, price, earnings

Example Request

A buyer reports a $50 payout for a 90-second call. Any of these three shapes works:

# Query string (Ringba-style postback)
GET https://v3.callscaler.com/api/v1/ppc/inbound-webhooks/{token}
  ?caller_id=%2B13055551234&duration=90&payout=50&ref_id=abc123

# Form-encoded
POST https://v3.callscaler.com/api/v1/ppc/inbound-webhooks/{token}
Content-Type: application/x-www-form-urlencoded

caller_id=%2B13055551234&duration=90&payout=50&ref_id=abc123

# JSON
POST https://v3.callscaler.com/api/v1/ppc/inbound-webhooks/{token}
Content-Type: application/json

{
  "caller_id": "+13055551234",
  "duration": 90,
  "payout": 50,
  "ref_id": "abc123"
}

Payout Handling

When a payout arrives, CallScaler:

1. Updates the call's value_cents to the buyer-reported amount and flags it value_from_webhook=true so reporting can distinguish real revenue from estimates.
2. Recomputes the publisher payout from the campaign's config — flat amount or rev-share percent of the new value — and credits the publisher's pending balance.
3. Buyer reported $0 (rejected the call)? The call is marked unqualified and the publisher credit is zeroed out.

Publisher rev-share lives entirely on the campaign assignment, not the webhook, so a single source of truth controls what each publisher earns regardless of which buyer reports the payout.

Duplicates within 15 minutes (same call + same payout) are a no-op — buyers can safely retry on network blips.

Security

Each webhook can require an X-Webhook-Secret header. Generate or rotate the secret from the webhook's editor — requests missing or mismatching the secret are rejected with 401.

Rate limits are enforced per source IP (50/min) and per webhook token (200/min) so a misbehaving buyer can't flood the matcher.

Recent Events & Auditing

Every inbound POST is logged to the webhook's Recent events panel (open the webhook from the Webhooks settings page). Each row shows the caller, duration, raw payout reported, matched call (with phone number), status (matched / unmatched), and the reason for any mismatch.

Use this to:
• Verify a buyer's postbacks are reaching you.
• Diagnose field-name mismatches when events show as unmatched.
• Confirm publisher credits actually applied after a buyer reports a payout.

Troubleshooting

Events showing as unmatched?
• Open the webhook editor and check the Recent events panel — the reason column tells you what failed.
• Most common: caller number format mismatch (we normalize to E.164 internally; make sure the buyer is sending the same caller). Or the buyer's ref_id field name isn't in the auto-detect list — override it in the webhook config.
• If duration is the issue, raise Match duration tolerance from the default 60s.

Returning 401?
• The webhook requires X-Webhook-Secret and the buyer isn't sending it (or is sending the wrong value). Rotate the secret from the webhook editor and re-share with the buyer.

Returning 429?
• You're hitting the rate limit (50/min per source IP, 200/min per webhook). Have the buyer batch postbacks or back off retries.

Webhook disabled?
• Pause toggle is on the webhook row in the Webhooks settings table. Disabled webhooks return 200 with {"ok":false,"reason":"endpoint disabled"} so buyers don't keep retrying.