# How customers and orders are attributed

> What we record about where each customer and order came from, how long we keep it, and how the "How did you first hear about us?" answer joins it.

Source: https://www.coritan.com/docs/organizations/attribution/

*Attribution* is the record of where a customer or an order came from. When a storefront that Coritan serves makes a customer or places an order, we write down the visit behind it: the campaign link or ad the visitor followed, the site that sent them, and the page they landed on. Your team reads it to see which campaigns and channels bring customers who buy.

Attribution needs the storefront's tracker, which sets its cookies on every visit. If you build your own storefront on the [Storefront API](/docs/organizations/storefront/storefront-api/), the API reads the same cookies from the requests your site forwards, and takes the details in the order itself when your site cannot forward cookies.

## What a visit records

The tracker keeps two *touches* for each browser: the first visit it ever saw, and the last visit that was not direct. A touch holds:

- The *source*, *medium*, *campaign*, *term*, *content* and *ID* of the `utm_` parameters in the link, in lower case for the source and medium.
- The ad click IDs in the link: `gclid`, `gbraid`, `wbraid`, `dclid`, `fbclid`, `ttclid`, `msclkid`, `rdt_cid`, `twclid`, `li_fat_id`, `epik` and `sccid`.
- The *referrer*, which is the site that sent the visitor, and the *landing page*, which is the first page they saw.
- A *type*: `paid` for a visit with a click ID or a paid medium (`cpc`, `ppc`, `paid`, `paid-social`, `display`, `retargeting`, `cpm`, `banner`), `utm` for any other link with a `utm_` parameter, `organic` for a visit from a search engine or social network, `referral` for a visit from any other site, and `direct`.

A visitor who types your address or follows a link from your own storefront does not replace the last touch. A visit that only prefetches a page records nothing.

## Channels

Every touch belongs to one channel, so reports group traffic the same way everywhere. The channel comes from the click ID first, then the medium, then the source and the referrer.

| Channel | A visit that |
| --- | --- |
| Paid Search | Has a Google or Microsoft click ID, or a `cpc`, `ppc` or `paid` medium from a search engine. |
| Paid Social | Has a click ID from a social network, a `paid-social` medium, or a `cpc`, `ppc` or `paid` medium from a social network. |
| Display | Has a `dclid`, or a `display`, `retargeting`, `cpm` or `banner` medium. |
| Organic Search | Comes from a search engine with no ad. |
| Organic Social | Comes from a social network with no ad. |
| Email | Has the medium `email`, `e-mail`, `newsletter` or `mail`. Your own [transactional email](#email-links) counts here. |
| Affiliate | Has the medium `affiliate`, or a `ref` parameter in the landing page address. |
| Referral | Comes from any other site. |
| Direct | Has no source we can name. |
| Other | Fits none of the rows above, such as a `cpc` medium from a source that is neither a search engine nor a social network. |

A paid touch also names its ad network: Google Ads, Microsoft Ads, Meta Ads, TikTok Ads, Reddit Ads or X Ads.

## What a customer records

When a customer is made through the storefront, we write one record for the account. It holds the first and last touch with their channels, the click IDs, how the account was made, the browser's device type and country, and the visitor's consent choice at that moment.

*How the account was made* is `email` for the registration form, `code` for a sign-in code sent by email, `checkout` for a guest checkout, or the provider for a social sign-in: `discord`, `github`, `google` or `microsoft`. An account your team creates, and an account that moved from your old site, has no record.

A sign-in with a social provider carries the visit through the provider's page, so a new customer is credited to the visit they started from.

## What an order records

Each storefront order, from a single plan or a basket, gets one record on its invoice. A basket has one invoice, so it has one record for all its services. We write it once: asking again for the same order never changes it.

An order's record holds:

- The first and last touch with their channels, and the click IDs.
- The Meta browser and click IDs (`fbp` and `fbc`), and the Google Analytics client and session IDs. When the browser has no `fbc` but the visit came from a Meta ad, we build it from the `fbclid` and the time of the click, as Meta describes.
- The IP address, the browser name and the device type, and the country.
- The consent choice, the coupon code and the coupon's campaign, and the affiliate or referral code when the browser holds one.
- Whether the customer was new: they had no earlier invoice that was paid for money. A free server does not count as a purchase, so a customer's first paid order still reads as new.
- Whether the order was free, which means its invoice total was zero.
- The customer's answer to "How did you first hear about us?", once they give it.

Orders that your team places for a customer, and renewals, have no record. An order placed in a support session that signs in as the customer has none either, because the visit belongs to your team.

If writing the record fails, we log the failure and the order goes through.

## How a customer's answer joins the record

After a customer pays for an order, or places a free one, the storefront asks "How did you first hear about us?" once. The question shows only in the browser that placed or paid the order, never from a link in an email. The customer taps one answer, and Other opens a field for up to 200 characters. The first answer stays; a second one does not replace it. The answer shows on the order, next to its channels. [Take the answer with the Storefront API](/docs/organizations/storefront/storefront-api/#ask-how-the-customer-heard-about-you) if you build your own checkout.

The answers are Google search, Google Ads, Bing or other search, Reddit, A forum, YouTube, Discord, Instagram, Facebook, TikTok, X, A friend and Other.

## Consent and privacy

The tracker sets its own cookies before a visitor chooses, because they only say how a visit arrived. Ad tags wait for consent. We copy the visitor's consent choice onto each customer and order record, so a later report or conversion can respect it.

- In the EEA, the United Kingdom and Switzerland, and wherever we cannot tell the country, both analytics and marketing count as denied until the visitor chooses. Elsewhere both count as granted until they opt out.
- A browser that sends Global Privacy Control has marketing denied everywhere, even after the visitor chooses to accept all.
- The record never holds the customer's name, email address or any payment detail.
- We keep an order's IP address for 30 days. After that a daily job cuts it to its network (the first three parts of an IPv4 address, the first three groups of an IPv6 address) and marks the record.
- The tracker's visit records are removed after 180 days. The attribution records stay for as long as the customer or the order does, and go when the organization is removed.
- A customer's privacy request export includes their account record and the record of each order. See [Export a customer's data](/docs/organizations/customers/#export-a-customer-s-data).

## Where your team sees it

The staff API answers it on the pages your team already uses:

- `GET /staff/orders` gives each order a `channel`, the channel of the last touch as `{key, label}`, or `null` for an order with no record.
- `GET /staff/orders/{service_id}` gives `attribution` for the order, and `GET /staff/customers/{customer_id}` gives it for the customer. Both are `null` when there is no record. They hold the `first` and `last` touch, each with its `channel`, ad `network` and `utm_` fields, the `click_ids` with their ad network, the device type, the country and the consent. The order adds the answer to the question, the coupon with its campaign, whether the customer was new and the order free, and which IDs we hold. It never sends the whole IP address: `address.state` says `kept` or `truncated`, and `address.network` gives the network.

See [Staff billing and orders](/docs/organizations/staff-console/billing/#work-with-orders-and-services) for the list's filters and the rest of the order answer.

## Read the acquisition report {#read-the-acquisition-report}

The acquisition report adds the records up: where your visits, sign-ups, orders and revenue came from over a period, set against the period before. Your team reads it in the staff console, in the Acquisition section of Insights ([Read the acquisition report](/docs/organizations/staff-console/billing/#read-the-acquisition-report) describes the page). A script reads the same answer from the API:

| Who | Call | Needs |
| --- | --- | --- |
| The organization's owner | `GET /api/v1/orgs/{org_slug}/acquisition` | The Owner or Admin role |
| Your team's console | `GET /api/v1/orgs/{org_slug}/staff/insights/acquisition` | The Billing role or higher |
| A platform administrator, for any organization | `GET /api/v1/admin/orgs/{org_id}/acquisition` | The `orgs.view` permission |

The three answer alike and read your data only. No address and no email is in the answer.

```bash
curl "https://api.coritan.com/api/v1/orgs/acme/acquisition?from=2026-09-01&to=2026-09-30&compare=previous&currency=USD&limit=10" \
  -H "Authorization: Bearer $TOKEN"
```

| Parameter | What it sets |
| --- | --- |
| `from` and `to` | The first and last day, `YYYY-MM-DD`, both included, in your brand's time zone. The last 30 days when left out. A period is at most 366 days. |
| `compare` | `previous` (the default) sets the totals against the same number of days just before the period, or the month before for a calendar month. `none` leaves the comparison out. |
| `currency` | A three letter code. Orders and revenue count in this one currency. When left out, it is the currency with the most revenue in the period, or your brand's own currency when there were no orders. |
| `limit` | The rows in each ranked list, 1 to 50 and 10 by default. The totals always cover every row. |

A `from` after `to`, a period over 366 days, a `compare` other than the two words, a `currency` that is not three letters and a `limit` outside 1 to 50 answer `422` and say what to change.

### What the report counts

- A *visit* is a stored session of your storefront with bots left out. We keep sessions for 180 days, so `visits_complete` is `false` when the period starts earlier, and the visit figures are then partial.
- A *sign-up* is a customer made through the storefront, counted on the day we wrote their record. A customer your team creates has none.
- An *order* is a storefront order that is not free and whose invoice is paid, counted on the day we wrote its record. A free server is a sign-up and a visit but never an order, and an invoice nobody paid is not an order yet.
- *Revenue* is the invoice total without tax, in the minor units of `currency` (cents for dollars). An order counts only in the currency of its invoice, so a euro order is never in a dollar report. `currencies` lists every currency with an order in the period, and money is never added across currencies.
- A *new customer* is an order that is the customer's first paid order.
- The *conversion rate* is orders over visits.

Every figure that ends in `_bp` is in basis points: 123 is 1.23 percent. The channel, source, medium, campaign and landing page of an order are those of its last touch.

### What the answer holds

`from`, `to`, `zone` (your brand's time zone), `currency`, `currencies`, `basis` (what the figures count, as the words `last`, `excluding_tax`, `first_paid_order` and `sessions_without_bots`) and `visits_complete`, then:

| Key | What it holds |
| --- | --- |
| `totals` | `visits`, `signups`, `orders`, `new_customers`, `revenue_minor` and `conversion_rate_bp`. `previous` holds the same figures for the period before, or `null` when `compare` is `none`. |
| `channels` | Each channel of the last touch with `key`, `label`, `visits`, `signups`, `orders`, `new_customers`, `revenue_minor` and `share_bp`, its share of the orders. |
| `first_touch` | The same figures, without visits, by the channel of the first touch. |
| `matrix` | Orders and `revenue_minor` for each pair of a `first` channel and a `last` channel. |
| `sources` | Each `source` and `medium` with its `channel`, `visits`, `orders`, `new_customers` and `revenue_minor`. |
| `campaigns` | Each `campaign` with its `source`, `medium`, `visits`, `orders` and `revenue_minor`. |
| `landing_pages` | Each `path` with `visits`, `signups`, `orders` and `revenue_minor`. The path has no query string, and no language prefix when your storefront has languages, so `/de/products` counts as `/products`. |
| `devices` | Each `device` (`desktop`, `mobile`, `tablet` or `unknown`) with `visits`, `orders` and `revenue_minor`. |
| `click_ids` | `orders`, `with_click_id`, `rate_bp` and `networks`, the orders for each ad network that a click ID names. |
| `heard_about` | The answers to "How did you first hear about us?" with `source`, `label`, `orders` and `share_bp`, the share of the answered orders. |
| `return` | The [ad spend return report](/docs/organizations/ad-spend/) for the period in `currency`, as `rows`, `totals` and `truncated`, or `null` when you have no ad spend in the period. |
| `truncated` | `true` when the return report read only the first 50,000 of your orders. |

A list ranks its rows by orders, then revenue, then visits, and holds the top `limit` of them.

### Open the orders behind a row

`GET /api/v1/orgs/{org_slug}/staff/orders` takes the exact filters `channel`, `first_channel`, `source`, `medium`, `campaign`, `device`, `heard_about` and `coupon_campaign`, each matching the order's record. An order with no record matches none of them. A `channel` or `first_channel` that is not one of the ten channels above answers `422` and lists them.

## Coupon campaigns

A coupon can name a *campaign* of up to 80 characters, such as `summer-launch`. An order that uses the coupon copies the campaign onto its record, so renaming the campaign later does not move old orders. See [Manage coupons](/docs/organizations/staff-console/billing/#manage-coupons).

## Email links

The links in your transactional emails carry `utm_source` with your organization's slug, `utm_medium=email` and `utm_campaign` with the template's name. They are the welcome email, the invoice emails (created, due tomorrow, overdue and paid), the payment failure emails, the service activated email and the free server emails. A visit from one of them counts as the Email channel.

Only links to your own storefront are tagged. A link that signs the reader in or proves something is never tagged: the password reset, the address confirmation and any link with a token, code or key in it. A link that already has a `utm_` parameter keeps it.

## Related

- [Add analytics and ad tags](/docs/organizations/tags/)
- [Read your storefront's traffic](/docs/organizations/traffic/)
- [Sign customers in to your storefront](/docs/organizations/storefront/customer-sign-in/)
- [Show your catalogue with the Storefront API](/docs/organizations/storefront/storefront-api/)

## API

- `GET /api/v1/orgs/{org_slug}/acquisition`: Acquisition report (https://www.coritan.com/docs/api/reference/organizations/organizations-members/acquisition/#op-get-api-v1-orgs-org-slug-acquisition)
- `GET /api/v1/orgs/{org_slug}/staff/insights/acquisition`: Acquisition report (https://www.coritan.com/docs/api/reference/organizations/org-staff-insight/#op-get-api-v1-orgs-org-slug-staff-insights-acquisition)
