# Attribute payments by email

> Credit provider payments to the right visit with the buyer's email when the checkout can't carry the visitor ID.

Source: https://metricraven.com/docs/attribute-by-email  
Markdown: https://metricraven.com/docs/attribute-by-email.md  
Section: Revenue

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

1. **Connect your payment provider**

   Payments still come from [Stripe](https://metricraven.com/docs/stripe), a provider webhook ([Lemon Squeezy](https://metricraven.com/docs/lemonsqueezy), [Polar](https://metricraven.com/docs/polar), [Paddle](https://metricraven.com/docs/paddle), [Dodo Payments](https://metricraven.com/docs/dodo-payments)) or the [Payments API](https://metricraven.com/docs/payments-api). The browser call only says who paid; it never creates revenue.

2. **Pass the buyer's email after a successful payment**

   ```javascript
   // 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](https://metricraven.com/docs/npm-package):

   ```javascript title="@metricraven/web"
   import { payment } from "@metricraven/web";

   payment({ email: user.email });
   ```

3. **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.

   ```html title="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](https://metricraven.com/docs/payments-api), add the customer's `email` (or `customer_email`) when you have no visitor or user ID:

```bash
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](https://metricraven.com/docs/node-sdk), pass `email` to `payment()`:

```javascript title="Server"
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](https://metricraven.com/docs/identify-users) 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](https://metricraven.com/docs/delete-person-data) by email or visitor ID removes their claims too.

## Security model

The call is self-asserted, like browser [identify](https://metricraven.com/docs/identify-users): 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. |

> **Warning:** 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 returns `false` for 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](https://metricraven.com/docs/attribution-guide-with-podia), [Kajabi](https://metricraven.com/docs/attribution-guide-with-kajabi), [Ghost](https://metricraven.com/docs/attribution-guide-with-ghost), [Lemon Squeezy](https://metricraven.com/docs/lemonsqueezy-other-methods), [Polar](https://metricraven.com/docs/polar-other-methods) and [Paddle](https://metricraven.com/docs/paddle-other-methods).
