PageDuel Help
Revenue attribution

Stripe revenue integration

Connect Stripe webhooks and attribute checkouts, invoices, and refunds.

Stripe revenue integration1Checkout metadata2Signed webhook3Normalize order/refund4Revenue report
Stripe revenue integration

What PageDuel counts

PaymentCounted revenue
Platform-kept paymentThe full amount
Destination charge with transfer_data.amountThe amount minus what was transferred out
Destination charge with application_fee_amountJust the fee (a separate application_fee.created event)
Direct charge on a connected accountJust the fee
Separate charges and transfersThe full charge (v1 — see Limits)
RefundSubtracted from the same payment, per the rule above

Setup

Create the PageDuel Stripe integration for a site and copy its webhook URL. In Stripe, subscribe to checkout.session.completed, invoice.paid, charge.refunded, payment_intent.succeeded, application_fee.created, and application_fee.refunded — do not use charge.succeeded, it isn't handled. Save the signing secret in PageDuel.

Note: on Stripe Billing API version 2025-03-31.basil or newer, an invoice.paid payload no longer carries the PaymentIntent id, so PageDuel can't merge an invoice with its payment_intent.succeeded. If you bill subscriptions through Stripe invoices on that API version, don't add payment_intent.succeeded to this endpoint yet, or subscription revenue counts twice. Older endpoint API versions are unaffected.

Optional: a Connected accounts endpoint

Marketplace and Connect platforms can add a second Stripe webhook at the same URL, scoped to Events on Connected accounts, subscribed only to payment_intent.succeeded. Paste its own signing secret into Connected accounts endpoint secret in PageDuel. This endpoint is attribution-only — it never adds revenue by itself — but it lets a direct charge on a connected account still be linked to the visitor who made it.

Shared Stripe account rules

If several PageDuel sites (or other products) share one Stripe account, add a rule so each site only counts its own payments: product IDs, a metadata key=value match, "description contains" (case-sensitive), or counting connected-account fees (optionally limited to one Connect application ID). A payment counts when it matches any rule you've set. With no rules, every payment counts, as before. Refunds always follow their original purchase. Tip: make at least one rule match on the PaymentIntent itself, not just the Checkout session — a filter checked only at Checkout can miss a later payment_intent.succeeded for the same sale.

Note: "Count fees from connected accounts" counts every application fee on the Stripe account — the Connect application ID only separates different Connect platforms, not different sites on the same platform. Turn it on for one site per Stripe account.

Browser and webhook together

If the site also calls PageDuel.trackPurchase(...) in the browser for the same sales, turn on Stripe is the revenue source so a sale isn't counted twice. With it on, the webhook amount is what counts as revenue; the browser purchase event still counts toward goals and funnels, and experiment revenue still uses the browser-reported amount.

Full attribution (optional)

Without this, sales still get counted, just under source stripe instead of a real visitor. To attribute a sale to the visitor who made it:

  • Checkout: client_reference_id: PageDuel.getVisitorId()
  • PaymentIntent: metadata: { pd_visitor_id: PageDuel.getVisitorId() } — for a direct charge on a connected account, set this on the PaymentIntent created on that connected account, and add the optional Connected accounts endpoint above so PageDuel can read it.
  • Browser: PageDuel.trackPurchase(amount, { paymentIntentId }), so PageDuel can link a webhook-recorded sale to the browser visitor who triggered it.

Limits

Separate charges and transfers count the full charge amount (v1). Refunds of a transfer_data.amount destination charge aren't subtracted — only the platform-kept and fee-based cases are. For a destination charge that used a fee, refund with refund_application_fee set so the fee refund event is what gets subtracted.

Note: a refund of a platform-kept payment that Stripe delivers before its original purchase is skipped on a site with filter rules configured (rare). Fee refunds aren't affected — they're matched on the fee rule alone, not on an existing purchase row.

Troubleshooting

A 400 Invalid webhook signature usually means the wrong secret is paired with the wrong endpoint — check which secret belongs to the main endpoint and which belongs to the Connected accounts endpoint, and rotate both if either was compromised.

On this page