Payout Sync

Pull real payout amounts from buyers and external networks (Ringba, MarketCall, Trackdrive) into CallScaler so every call's revenue reflects what you actually earned. Two transports: inbound webhook (push) and Ringba API (pull).

Overview

Payout Sync brings the dollar amount a buyer or network paid you onto the matching call in CallScaler — so reports, dashboards, and publisher rev-share all run on real revenue instead of estimates.

Two transports under one feature:
Inbound Webhook (push) — buyers POST a payload to a CallScaler URL when they pay you. Works with Ringba (postback URL), MarketCall, Trackdrive, Arroyo, and any custom buyer system that can fire a webhook.
Ringba API (pull) — Ringba doesn't push webhooks for payouts the same way other networks do, so we poll the Ringba API every 15 minutes using an access token you provide. Match logic and downstream payout handling are identical to the webhook side.

Who this is for:
• You sell calls to a buyer or network that pays per qualified call.
• 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.

Where to Set It Up

All transports live on a single page: Extensions → Payout Sync. The page is gated to paid plans (Pro and above).

Inbound Webhook (push)

Use this when your buyer's platform can fire a webhook on payout settlement.

1. Open Extensions → Payout Sync.
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.

Ringba API (pull)

Ringba doesn't push payout webhooks the way most other networks do, so CallScaler pulls payouts directly from the Ringba API every 15 minutes.

1. In Ringba, go to Security → API Access Tokens and click Add API Access Token. Copy the token immediately — Ringba only shows it once.
2. In CallScaler, open Extensions → Payout Sync and click Connect Ringba.
3. Paste the token and click Verify. CallScaler lists every Ringba account the token can see.
4. Check the boxes for the accounts whose payouts you want to sync (you can select multiple).
5. Click Connect & sync. CallScaler runs an initial sync immediately and then polls every 15 minutes from then on.

Click Sync now any time you want to force an out-of-cycle pull. Use the Enabled toggle to pause without disconnecting.

Only Ringba calls with hasPayout=true and a non-zero payoutAmount are applied. Calls that had a bid but didn't qualify for payout are ignored — same as Ringba's own reporting.

How Matching Works

Both transports use the same matching logic. CallScaler tries strategies in this order — first hit wins:

1. ref_id — If the buyer echoes back our outbound ping ref ID (inbound webhook only). Most reliable.
2. call_id — If the buyer echoes back our internal call UUID (inbound webhook only).
3. Ringba inbound call ID — For the Ringba API pull, we use Ringba's inboundCallId as the idempotency key so the same payout isn't applied twice.
4. caller + duration + start time — Fallback heuristic shared by both transports: same caller number (normalized to E.164), duration within ±60 seconds (the buyer's leg is typically ~30s shorter than ours due to ring/IVR), within a time window of the reported call start. The webhook side uses a configurable window (default 24h); the Ringba pull uses ±2 minutes because Ringba reports exact start times.

If nothing matches, the event is logged with a reason — visible in the source's recent-activity panel so you can debug field mismatches.

Recognized Field Names (Inbound Webhook)

Inbound webhooks accept 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:

You only need to override field names if your buyer uses an unusual schema. Common platforms (Ringba postback, MarketCall, Arroyo, Trackdrive) work out of the box.

  • 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 (Inbound Webhook)

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

Once a payout matches a call — by either transport — CallScaler:

1. Updates the call's value_cents to the 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 on the webhook side (call rejected)? The call is marked unqualified and the publisher credit is zeroed out. Ringba's API only reports calls with a non-zero payout, so this case only applies to webhooks.

Publisher rev-share lives entirely on the campaign assignment, not the source config — so a single source of truth controls what each publisher earns regardless of which transport delivers the payout.

Both transports are idempotent. Webhook duplicates within 15 minutes (same call + same payout) are a no-op. Ringba pull tracks each inboundCallId on the call row, so re-running a sync never double-credits.

Security

Inbound webhooks 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.

Ringba API tokens are stored AES-256-GCM encrypted at rest using a key that lives in the server environment, not the database. Disconnect at any time from the Payout Sync page to drop the row entirely. If Ringba rejects the token (you rotated it on their side), CallScaler automatically disables the integration and surfaces a reconnect prompt.

Recent Activity & Auditing

Every webhook POST is logged to that webhook's Recent events panel (open the webhook from the Payout Sync page). Every Ringba sync attempt is logged to the integration's Recent sync activity panel. 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 (or Ringba's API records) are reaching you.
• Diagnose field-name mismatches when events show as unmatched.
• Confirm publisher credits actually applied after a payout was reported.

Troubleshooting

Webhook 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.

Ringba sync events showing as unmatched?
• The call was likely outside our ±2 minute start-time window or ±60s duration tolerance. Verify the call actually exists in CallScaler with the same caller — sometimes calls forwarded through a non-CallScaler tracking layer never reach us.

Inbound webhook 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.

Inbound webhook 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.

Ringba sync shows "Ringba rejected this token"?
• Token was deleted or rotated in Ringba. Generate a new one and reconnect.

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