# Revenue attribution

Which campaign, automation step or A/B version an order followed — last touch, within a window — and where SendBeam shows what your email earned.

Opens and clicks say whether an email was read. Revenue says whether it worked. Once your store
sends SendBeam its orders, each order is credited to the campaign or automation email that
preceded it, and the figure appears wherever you would look for it: on the campaign, on the
automation step, on each version of an A/B test, and added up across every workspace on your
account. Nothing has to be configured — an order that arrives is attributed as it arrives.

## How an order reaches SendBeam

Orders come in as `order_placed` events on the
[e-commerce events endpoint](https://sendbeam.io/docs/ecommerce) — the same call that fires the
**Order placed** trigger. A Shopify store posts them from its own
**Order creation** webhook; anything else (n8n, Zapier, your own code) posts the
shape below with an API key that has the **E-commerce events** permission.

```
curl -X POST https://sendbeam.io/api/v1/ecommerce/events \
  -H "x-api-key: sb_live_XXXXXXXX_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "order_placed",
    "email": "jane@example.com",
    "external_id": "1001",
    "value": 84.50,
    "currency": "GBP",
    "placed_at": "2026-09-22T10:14:00Z"
  }'
```

Three fields matter for revenue, on top of the event itself:

- **`external_id`** (or `order_id`) — the order's id in your store.
  This is what makes an order an *order* rather than an event: with it, the order is
  stored once, a retried delivery is a no-op, and it can be attributed. **Without it the event still fires your automations and moves `lifetime_value`, but nothing is stored and nothing is attributed.** Shopify's webhook always carries one; if you post
  events yourself, include it. The [WordPress plugin](https://sendbeam.io/docs/wordpress) reports
  WooCommerce orders without an id at present, so those fire your automations and update
  `lifetime_value` but are not counted as revenue; to count them today, post the order
  with its id from your own code or an automation tool.
- **`value`** and **`currency`** — the order total in the
  store's own currency. Also required: an order without a total is not stored. The currency stays
  with the order; see [Currencies](#currencies).
- **`placed_at`** — when the order was placed. The windows below are
  measured back from this moment; it defaults to the time the event arrives.

The order is recorded against the contact whose email address it carries. If nobody with that
address is in the workspace yet, a contact is created for them, the same way any other store event
creates one. If the address is on your suppression list, or the workspace is at its contact cap,
the event is acknowledged but nothing is stored — [the response says which](https://sendbeam.io/docs/ecommerce#anything-else).
An order whose contact never received an eligible email is stored but credited to nothing, and
appears in no revenue figure.

## What counts

Attribution is **last touch, within a window**: the most recent email to that
contact that they engaged with shortly before ordering gets the credit. Precisely:

- An email the contact **clicked** within the **7 days** before the
  order wins. If they clicked more than one, the most recent click wins.
- Failing that, an email the contact **opened** within the **1 day**
  before the order wins — the most recent open.
- A click always beats an open, even when the open happened later. Clicking is the stronger
  signal.
- A **campaign** send and an **automation** send are both eligible. An
  automation's email is credited to the step that sent it, so a flow with three emails shows
  which one sells.
- Transactional and notification emails — a receipt, a password reset, a form notification —
  are never credited. A receipt did not earn the order it is a receipt for.
- For a campaign with an [A/B test](https://sendbeam.io/docs/campaigns/creating#subject-line-test), the
  version the contact received is recorded with the order, so the test can be judged on what
  each version earned.

An order is settled once and never re-attributed. An order with no eligible email in the window
is recorded as unattributed and stays that way. An order counts at the total your store sent
with it: there is no event yet for a refund or a cancellation, so the figures are gross — see
[What this is not](#honest-scope).

> This is the standard model every email platform uses, and it is deliberately approximate. It
> says "this order followed this email", not "this email caused this order". Treat the figures
> as a comparison between campaigns and versions, which is what they are good at.
> 

## Where the figure appears

- **The campaign page** — a **Revenue** tile beside Sent, Opened and
  Clicked, with the number of orders under it. The same figure is in
  `GET /api/v1/campaigns/{id}/report` as `revenue`, with
  `by_currency` listing each currency separately.
- **The automation page** — each **Send email** step shows what it
  earned and how many orders, so you can read down a flow and see where the money is.
- **A/B tests** — each version's box shows its revenue and orders alongside its
  open and click rates. When you set a test up, the winner can be chosen by
  **revenue per recipient** instead of opens or clicks: the version whose test
  recipients spent the most, per person, goes to everyone else. A revenue test with no orders by
  the time the wait is up is a tie, and a tie goes to A.
- **Insights** — [Account → Insights](https://sendbeam.io/docs/analytics/account-insights)
  adds it up across every workspace on the account for the last 30 days, with a column per
  workspace, so an agency can read what email earned across all its client stores.
- **Segments** — [segments](https://sendbeam.io/docs/segments) can be built on buying too:
  whether someone has ordered, and how recently.

Each of these appears only once an order has actually been attributed. A workspace without a
store, or one whose orders have not followed any email yet, shows no revenue figure at all —
rather than a zero that would claim its email earned nothing.

## Currencies

Amounts are **never converted** and never added across currencies. A store selling
in pounds and one selling in euros have no common total without an exchange rate and a date, and
inventing one would give you a confident number that is simply wrong. Where a figure covers
more than one currency — an account whose workspaces sell in different currencies, or a store
that takes several — the largest is shown as the headline and the others are listed beside it.
The order count covers them all. In the API, `total` is the largest currency and
`by_currency` has every one.

## What this is not

- Not a causal claim. Last touch within a window is an attribution rule, not proof.
- Not anonymous tracking. An order is credited only when the buyer's email address is a
  contact who received an email — there is no cookie, pixel or session stitching behind it.
- Not adjustable yet. The 7-day click and 1-day open windows are fixed; every campaign and
  every workspace is measured the same way, which is what makes the figures comparable.
- Not net of refunds. There is no event yet for a refund or a cancellation, so an order that
  is later refunded or cancelled in your store stays in the figure at the total it was sent
  with.
- Not retroactive. An order that arrived without an `external_id` before your
  integration sent one was never stored, and cannot be attributed later.

---
Source: https://sendbeam.io/docs/ecommerce/revenue
