Tracking & Attribution

How Do You Attribute Payments From a Processor Nobody Integrates (Paddle, Lemon Squeezy, PayMongo)?

No native connector for Paddle, Lemon Squeezy or PayMongo? Relay the payment webhook, match it by visitor ID or email, and attribute the revenue.

Quick answer

Send the payment to your attribution tool yourself. Nearly every processor can POST a webhook when a charge succeeds, and a generic inbound purchase endpoint will accept it — so the "missing integration" is really a small relay carrying five fields: a stable order ID, the amount, the currency, the buyer's email, and a status. Echo your own visitor ID back through the processor's metadata field and the match gets stronger still. Once the payment is inside, it matches to a session the same way a Shopify order does.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Send the payment to your attribution tool yourself. Nearly every processor can POST a webhook when a charge succeeds, and a generic inbound purchase endpoint will accept one — so the missing integration is really a small relay: a Zapier step or twenty lines of server code carrying an order ID, amount, currency, email and status.

That relay is the integration. Below: what goes in it, and what each missing field costs you.

Why does your attribution tool not know the payment happened?

Because nothing in the browser told it. Native connectors exist for the handful of platforms every vendor builds first — Shopify, WooCommerce, Stripe, a CRM or two. Outside that list, the money moves through a checkout your tracking has never seen.

Three things go wrong at once with a merchant of record like Paddle or Lemon Squeezy, and with regional gateways like PayMongo:

The checkout is on someone else’s domain. The buyer leaves your site for an overlay or a hosted page, and your first-party cookie, visitor ID and session all stop at that boundary. It is the same hop that makes ad conversions show up as Direct, except there is no pixel on the other side to pick the thread back up.

The confirmation page is generic. A success page hosted by the processor carries no tag of yours, so “fire a purchase event on the thank-you page” has nowhere to fire. Even with a redirect back to your site, a closed tab or a banking app taking over means the event never happens.

The only reliable record is server-side. The processor knows the moment the card clears. Your website does not, and may never. That asymmetry is the whole problem — and the whole solution, because the server-side record is the sturdier one.

What actually breaks when revenue lands outside the tracking stack?

Your reports keep working, which is why this goes unnoticed for months. Leads, sessions and cost per lead all look fine. Only the revenue column is wrong, and it is wrong in the direction that costs money.

  • Every channel looks equally good. With no revenue attached to a source, you compare on lead volume and cost per lead — so cheap unqualified signups beat buyers.
  • The ad platforms get nothing to learn from. No purchase event means no purchase-optimised bidding; the algorithm keeps chasing the last signal it saw, usually a form submit.
  • Refunds never net out. A payment that reverses two weeks later still sits in your spreadsheet, inflating the winner.
  • Finance and marketing hold different numbers, and neither side can reconcile without a join key.

The size of the miss scales with how much revenue runs through that processor — on a merchant of record, usually all of it.

What does a payment need to carry to be attributable?

Five fields, plus one that changes everything. Miss any of the five and the payment cannot be recorded properly; miss the sixth and it is recorded but not confidently matched.

Field Why it matters Without it
Order / transaction ID The idempotency key — how a retry is recognised as the same payment Duplicates on every retry
Amount and currency The value that reaches reporting and the ad platforms Revenue reports as zero, or in the wrong currency
Buyer email The strongest identifier most processors will give you Match falls back to phone or IP
Event timestamp Places the purchase in the journey and the reporting period Lag and cohort analysis break
Status (paid / refunded) Lets a reversal net against the original Refunds inflate attributed revenue
Your visitor ID, echoed back Ties the payment to the exact session that produced it Falls back to identity matching — usually fine, sometimes wrong

That last row is worth the engineering effort. Most checkouts accept a custom metadata field — a key-value bag stored with the transaction and returned on the webhook. Read your tracking cookie before sending the buyer to checkout, write the value into that bag, and the payment comes back naming its own session — no fuzzy matching required.

// Read the first-party visitor ID, pass it as checkout metadata.
const vid = document.cookie.match(/(?:^|;\s*)pl_vid=([^;]+)/)?.[1];
const url = new URL('https://checkout.example-processor.com/buy/plan-pro');
if (vid) url.searchParams.set('checkout[custom][visitor_id]', vid);
window.location.href = url.toString();

Whatever the processor calls it — metadata, custom data, passthrough, reference — it survives the payment and comes back on the webhook. Confirm the exact parameter name in your processor’s docs: the mechanism is near-universal, the spelling is not.

How do you get the payment out of a processor nobody integrates?

Three routes, most reliable first.

Route 1 — the processor’s webhook, straight to a generic purchase endpoint. Every processor fires a server-to-server notification when a payment succeeds. Point it at your tool’s inbound purchase endpoint, or at a small function that reshapes the payload first. No browser involved, so it survives ad blockers, closed tabs and failed redirects.

Route 2 — an automation platform as the relay. Zapier, Make or n8n subscribe to the event and POST the normalised body onward. Slower and rate-limited, but no server to run. Same pattern as getting Facebook Instant Form leads into an attribution tool, applied to money.

Route 3 — fire from your own success page. Only where the processor redirects back to a page you control and passes the transaction reference, and only as a fallback: it needs a browser to finish a journey that already left your site.

A minimal Route 1 relay:

// Processor webhook → your relay → your tool's purchase endpoint
export async function handler(req) {
  const e = await req.json();
  if (e.type !== 'payment.succeeded') return new Response('ok', { status: 200 });

  await fetch(process.env.PURCHASE_ENDPOINT, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      order_id:    e.data.id,                       // idempotency key
      status:      'paid',                          // 'refunded' on a reversal
      value:       e.data.amount / 100,             // major units, not cents
      currency:    e.data.currency,
      email:       e.data.customer?.email,
      phone:       e.data.customer?.phone,
      visitor_id:  e.data.custom_data?.visitor_id,  // echoed cookie value
      occurred_at: e.data.created_at,
    }),
  });
  return new Response('ok', { status: 200 });
}

Two easily-missed details: send major units, or a processor reporting cents books a $49 sale as $4,900; and return 200 fast for ignored events.

How do you match that payment to the session that caused it?

By identifier strength, in tiers. A payment carrying your echoed visitor ID is a certainty; one carrying only an email is a strong probability; one carrying only an IP address is a guess you should label as such.

  1. Visitor ID echo — the payment names the browsing session outright.
  2. Normalised email — lowercased and trimmed, Gmail dot and plus variants folded together so a.b+shop@gmail.com and ab@gmail.com resolve to one person.
  3. Normalised phone — E.164, digits only, country code inferred from session geography.
  4. IP and user agent in a time window — the weakest tier, and the one to distrust on shared networks.

Once a payment lands on a session, the session belongs to a person, and that person’s history supplies the attribution. This is where visitor identity resolution does the real work: the buyer who clicked a Meta ad in March, returned direct in April and paid through a hosted checkout in May is one cluster, not three strangers. The purchase inherits the whole chain — the difference between attributing ecommerce revenue to the ad that started it and writing “Direct” in a spreadsheet.

Diagram mockup: a payment webhook relayed into an inbound purchase endpoint, then down a four-tier match ladder from visitor ID to email, phone and IP, ending on a matched lead row

What about refunds, duplicates and mixed currencies?

Get all three right at ingest; reporting cannot fix them later.

Duplicates. Processors retry until they get a 200, and some deliver the same event twice in normal operation. Idempotency on the order ID plus status phase stops a retry becoming a second sale. Never dedupe on amount and email — two genuine purchases of the same plan collapse into one.

Refunds. A reversal needs its own record that nets against the original, not a deletion of it. Deleting loses the fact that the sale happened; ignoring it leaves you scaling against money you gave back. Same failure that makes refunds inflate attributed revenue on platforms that do have connectors.

Currency. A merchant of record settles in the buyer’s currency, so one week can span five. Store the native amount and currency code; convert only at reporting time.

How does PartialLeads attribute a payment from a processor it does not integrate?

Through a universal inbound webhook: anything that can POST an order becomes a purchase source, running the same pipeline as a native Shopify or Stripe connection. There is no second-class path for “unsupported” money.

It normalises identity first. Email lowercased and trimmed, phone to E.164 with a country-code fallback from session geography — so the person who typed a local number on your form and a formatted one at checkout resolves to one record.

It dedupes before it records. Idempotency is keyed on client, source, order ID and status phase. A redelivered webhook physically cannot create a second purchase, and a refund arrives as its own row, netting against the paid one in the Purchases ledger.

It matches to a session in tiers — visitor ID echo, then email, then phone, then IP — flips the lead from Partial to Completed, attaches the revenue, and writes a first-touch and a last-touch row, so the Attribution report shows first, last and resolved models side by side.

It fans the conversion back out. A matched purchase dispatches server-side to Meta, Pinterest, TikTok and Google Ads, plus your CRM webhooks — so a payment taken through a processor no ad platform has heard of still reaches the Conversions API with hashed identifiers attached. That is what a spreadsheet reconciliation can never do.

Dashboard mockup: a Purchases ledger with matched and unmatched payments and a refund netting against a paid row, beside a channels table showing revenue and ROAS

The honest constraints. You build the relay. PartialLeads accepts the POST; it does not log into your processor and pull payments, and there is no pre-built Paddle, Lemon Squeezy or PayMongo connector to click. Match quality is capped by your payload: with no email, no phone and no echoed visitor ID, a payment lands as an anonymous purchase. Those are not discarded — they sit in the ledger, become matchable when identity arrives, and can be re-matched by hand — but they add nothing to channel reporting until then.

What breaks The mechanism Where you see it in the dashboard
No native connector for the processor Universal inbound webhook — anything that can POST an order is a purchase source Integrations → Universal
The payment has no session attached Tiered match: visitor ID echo, email, phone, IP Lead flips Partial → Completed, revenue attached
The buyer looks like a stranger at checkout Six-tier identity cluster unions sessions and payments Customer Journey timeline
A retried webhook creates a second sale Idempotent dedup on client, source, order ID, status phase Purchases ledger — one row per payment
A refund still counts as revenue Refund row carries its own status phase, netting against the paid row Purchases ledger — net revenue
The ad platform never learns about the sale Server-side fan-out to Meta, Pinterest, TikTok, Google Ads CAPI activity log, API column
Payments settle in several currencies Native currency stored per purchase, converted at reporting time Attribution report — currency selector

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources

https://developers.facebook.com/docs/marketing-api/conversions-api https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/custom-data https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events


Frequently asked questions

QCan you attribute revenue from a payment processor your attribution tool does not support?
Yes, in most cases without waiting for anyone to build a connector. If the processor can send a webhook when a payment succeeds, and your attribution tool accepts a generic inbound purchase, a small relay between the two carries the order ID, amount, currency, buyer email and status. That is functionally the same integration a native connector provides.
QWhat is a merchant of record, and why does it break tracking?
A merchant of record like Paddle or Lemon Squeezy sells to your customer on your behalf and handles tax and compliance. Practically, it means the checkout is hosted on their domain, so your first-party cookie and session end at the hop and the confirmation page carries none of your tags. The payment record only exists server-side.
QHow do I pass my visitor ID through a hosted checkout?
Read your tracking cookie before you send the buyer to checkout and write the value into the processor's custom metadata field — most call it metadata, custom data, passthrough or reference. It is stored with the transaction and returned on the webhook, so the payment comes back naming the exact session that produced it. Check your processor's docs for the exact field name.
QWhat if the payment has no email on it?
Then it drops to weaker identifiers — phone if you have one, IP and user agent inside a time window if you do not — and the confidence of the match drops with it. A payment with no email, no phone and no echoed visitor ID lands as an anonymous purchase: still recorded in revenue, but contributing nothing to channel attribution until an identifier turns up later.
QWill a retried webhook double-count my revenue?
Not if the record is idempotent on the transaction ID plus the status phase. Processors retry until they get a 200 and some deliver duplicates in normal operation, so this is a design requirement, not an edge case. Never dedupe on amount and email instead — that would collapse two genuine purchases of the same plan by the same customer into one.
QShould the relay run on my server or in Zapier?
Your own endpoint is more reliable — no rate limits, no queue delays, and you control the retry behaviour. An automation platform is a legitimate substitute when you have no server to run, and it is often the faster thing to ship this week. Either way the payload is the same; only the thing doing the POST changes.
QCan a payment from an unsupported processor still be sent to Meta's Conversions API?
Yes, once it is matched to a session. The processor is irrelevant to the ad platform — the Conversions API takes hashed identifiers and a value, not a payment provider name. What matters is that the match supplied an email, phone or click ID to hash. A payment matched only by IP will send, but with much weaker match quality.
QHow far back can I import historical payments from a processor?
Revenue reporting can accept them at whatever date they occurred. Sending them onward to ad platforms is the constrained part — Meta clamps event times to a seven-day window, so older conversions still deliver but with a clamped timestamp, and they will not retroactively repair an old reporting period. Backfill for your own reporting, not to fix the ad platform's history.
QDoes PartialLeads have a Paddle or Lemon Squeezy integration?
Not as a named connector. It has a universal inbound webhook that accepts a posted order from any system, and from there the payment runs the same pipeline as a native Shopify or Stripe connection — identity normalisation, idempotent dedup, tiered session matching, first- and last-touch rows, and server-side fan-out to the ad platforms. You supply the POST; everything after it is identical.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.