Revenue
Attribute payments by email
On this page
Some checkouts can't carry the visitor ID: Podia, Kajabi, Ghost, hosted pages and third-party tools. On the page buyers see after paying, tell MetricRaven which email this visitor paid with. A payment from your connected provider with that customer email is then credited to the visit.
Call payment() on the thank-you page
Connect your payment provider
Payments still come from Stripe, a provider webhook (Lemon Squeezy, Polar, Paddle, Dodo Payments) or the Payments API. The browser call only says who paid; it never creates revenue.
Pass the buyer's email after a successful payment
script.js// On the page buyers see after paying, with the email they paid with. window.metricraven.payment({ email: "customer@example.com" });window.analytics.payment({ email })is the same call. With the npm package:@metricraven/webimport { payment } from "@metricraven/web"; payment({ email: user.email });Make the call safe before the script loads (recommended)
Add this to the page's
<head>, above other scripts. Calls made before the MetricRaven script loads are queued and sent as soon as it does.Queue stub<script> // Keeps the call until the MetricRaven script has loaded, then replays it. window.metricraven = window.metricraven || { q: [] }; window.metricraven.payment = window.metricraven.payment || function () { window.metricraven.q.push(["payment"].concat([].slice.call(arguments))); }; </script>
Verify it works
Make a test purchase from a fresh browser. Once the provider's payment arrives, it appears in Revenue credited to that visit's source instead of Unattributed. The order doesn't matter: the call can come before or after the payment reaches MetricRaven.
Send the email from your server
If you record payments with the Payments API, add the customer's email (or customer_email) when you have no visitor or user ID:
curl --request POST 'https://metricraven.com/api/v1/payments' \
--header "Authorization: Bearer $METRICRAVEN_SECRET_KEY" \
--header 'Content-Type: application/json' \
--data '{
"amount": 49,
"currency": "USD",
"transaction_id": "order_123",
"email": "customer@example.com"
}'With @metricraven/node, pass email to payment():
import { payment } from "@metricraven/node";
await payment({
amount: 49,
currency: "USD",
transactionId: order.id,
email: order.customerEmail, // matched, never stored as text
});The email first matches a visitor you linked with a server-side identify call, then a browser that called payment() around the payment time.
How matching works
| Rule | What it means |
|---|---|
| Last resort only | A claim is used only for a payment with no visitor ID, no user ID and no email that matches a server-side identify. It never replaces any of those. |
| One-hour window | It matches payments created up to an hour before or after the call, by the provider's payment time. |
| First claim wins | Repeat calls from the same browser are ignored and don't move the window. After the hour, a new call starts a new window. |
| Contested claims match nothing | If a second browser claims the same email within the hour, nobody gets the payment, and anything already credited through that claim goes back to Unattributed. |
| Renewals follow | Once a customer's payment is matched, their later renewals and subscription goals keep the same visit, as with any other link. |
| Rate limited | Each visitor can vouch for at most 3 emails a day, and each IP address can make 5 calls an hour. |
| Provider | Where the customer email comes from |
|---|---|
| Stripe | The customer's email, else the charge's billing or receipt email. |
| Lemon Squeezy | The order's user_email. |
| Polar | The order's customer email. |
| Paddle | Transactions only carry a customer ID. Select customer.created and customer.updated in your notification destination so MetricRaven can match them. |
| Dodo Payments | The payment's customer email. |
| Payments API | email or customer_email. |
Privacy
- Emails are never stored in plain text. MetricRaven keeps a keyed hash (HMAC-SHA256 with a server secret, different for every site), for three days.
- Nothing is stored in the browser, and no event is recorded for the call.
- The response is the same whether or not anything matched, so the call can't be used to test whether an email bought something.
- Deleting a person by email or visitor ID removes their claims too.
Security model
The call is self-asserted, like browser identify: anyone who can run JavaScript on your pages can make it. The rules above keep the damage small.
| A malicious browser | Can it? |
|---|---|
| See revenue, payments or whether an email bought something | No. The call returns nothing about payments. |
| Create, change or refund revenue | No. Revenue comes only from your provider or your server. |
| Override a visitor ID passed at checkout, a user ID, or a server-side identify | No. Claims only fill in payments that have no other link. |
| Credit someone's payment to its own visit | Only if it knows the buyer's email, claims it within an hour of their purchase, and the buyer's own thank-you page never makes the call. If the buyer's page does call, the claim is contested and the payment stays unattributed. |
The worst case is a payment credited to the wrong traffic source, never lost or extra revenue. When you can, pass the visitor ID at checkout or identify buyers from your server: both are exact and can't be claimed by a browser.
Details
- Emails are compared trimmed and case-insensitively. The browser call accepts a string or
{ email }and returnsfalsefor an invalid email. - Without analytics consent the visitor ID is daily; a payment matched to it still counts toward that visit's source. When consent is granted later the same day, the claim moves to the persistent ID.
- Guides: Podia, Kajabi, Ghost, Lemon Squeezy, Polar and Paddle.