Skip to main content

Campaigns

The Campaigns tab answers one question: how is this campaign doing, counting everything it touched — the clicks on your short links and the sessions on your site.

Find it under Analytics → Campaigns.

The Two Surfaces​

This is the thing to understand before reading any number on the page.

A campaign leaves traces in two different places, and they are not the same trace:

SurfaceWhere it comes fromWhat it counts
ClicksShort links carrying UTM parametersSomeone clicked your LinkForty link
SessionsThe web pixel on your siteSomeone browsed your site with those UTMs on the URL

Older campaign reporting showed only the first. That misses an entire ordinary case: a campaign whose UTMs are appended to the landing page URL and never touch a short link at all. Those campaigns had zero rows — not low numbers, no presence whatsoever.

The Campaigns tab joins both, so a campaign appears if either surface saw it. A row with clicks and no sessions means the pixel never saw those visitors. A row with sessions and no clicks means the campaign ran entirely through direct links.

Which number answers which question
  • "How many people did my link reach?" → Clicks
  • "How many people actually landed on my site?" → Sessions

They are different populations, not two measurements of one. A click that never becomes a session is a drop-off worth investigating.

Grouping​

Switch between Campaign, Source, and Medium with the toggle. This changes which UTM parameter the rows are grouped by — utm_campaign, utm_source, or utm_medium.

utm_term and utm_content are not grouping options here. They reach your destination URL but are not recorded on the click row.

Reading the Table​

ColumnMeaning
ClicksShort-link clicks carrying this UTM value, bots excluded
InstallsApp installs attributed to those clicks
SessionsDistinct site sessions the pixel recorded with this UTM value
ConversionsSite events matching your workspace's conversion goals
Conv. rateSee below — the denominator changes
RevenueCombined in-app and site revenue, per currency

The Installs column is hidden when no campaign in range has any. A web-only workspace would otherwise be reading a permanently empty column.

Conversion rate has two possible denominators. It uses conversions over sessions when the campaign has sessions, and installs over clicks when it does not. When there is nothing to divide by, it shows an em dash rather than 0% — "nothing to measure" is not the same as a measured zero.

Revenue is reported per currency, never summed across them. A campaign earning ¥40,000 and $300 shows both; adding them would be meaningless.

One Value in Detail​

Click any row to open that value's detail. The tiles at the top are the row's own numbers. Below them:

  • Trend — clicks, sessions and conversions per day across the selected range (installs appear when there are any). Two units share the chart, so the caption above it applies here too.
  • Top links — the links whose clicks carried the value, with their sessions, conversions and revenue. Click one to make it the page's selected link. Hidden when a link is already selected.
  • By source / By medium / By campaign — how the value splits across the two UTM dimensions you did not group by. Not shown for the Parameter grouping, which has no other dimensions.

Hovering a value shows its UTM Library card; copying or editing from there does not open the detail. All campaigns (or sources, or mediums) returns to the table.

Where Campaign Values Come From​

A click row records a UTM from either of two places:

  1. The short URL itself — https://go.example.com/sale?utm_campaign=spring
  2. The link's own UTM settings — configured on the link or its template

Values on the URL win per key. A link configured with utm_source=newsletter whose URL carries ?utm_source=partner records partner, because the value a sharer put on the URL is more specific than the link's stored configuration.

Each key resolves independently, so a link that configures only utm_source still records a utm_campaign that arrives on the URL.

Blank values never appear as rows

Empty and whitespace-only UTMs are treated as absent on both surfaces. They are excluded before the two surfaces are joined, so the table never shows a blank-labelled row.

What Is Excluded​

Some traffic is deliberately absent, and its absence is not a bug:

  • Bot clicks. Classified at ingestion and excluded from every campaign figure.
  • SDK-created shared links. Links your app users generate through the SDK are not campaign traffic; see Shared Links for their own reporting.
  • Untagged traffic. A link with no UTMs anywhere cannot be grouped and does not appear.

The table shows the top 100 values, ordered by combined revenue, then sessions, then clicks.

The link selector at the top of the Analytics page applies to the Campaigns tab like every other tab. With All Links the table covers the workspace; with one link selected it shows only the UTM values that link's clicks carried, and only site sessions attributed to it by its click — so a link's row is smaller than the workspace row for the same value (see Scoping to One Link below). Changing the link clears any row filter you had applied.

Time Range​

The range selector applies to both surfaces at once. How far back you can look depends on your plan's analytics retention — see the plan comparison for the current windows.

Nothing Showing Up?​

Campaigns appear once your links carry UTM parameters, or once the site pixel records visits with UTMs on the URL. If the table is empty:

  • Confirm the links you are running actually carry UTMs — see UTM Parameters.
  • If you expect sessions but see none, check that the web pixel is installed and that its allowed-domains setting includes the site.
  • If you expect clicks but see none, check that the traffic is not being classified as bot traffic, and that the time range covers when the campaign ran.
  • A 403 on this tab means your role lacks the analytics permissions it requires. Both install and event analytics permissions are needed.

API​

The same data is available at GET /api/analytics/campaigns, which accepts an API key as well as a session:

ParameterValuesDefault
days1–36530
groupBycampaign, source, medium, paramcampaign
paramKeythe parameter name to group by — required when groupBy=param—
valuefilter to one value—
projectIdscope to one project—
linkIdscope to one link; wins over projectId. An id outside your workspace is a 404—

With linkId, the click side counts only that link's clicks and the web side counts only sessions that were attributed to that link by its click — never sessions matched to it by UTM guesswork. That makes a link's row smaller than the workspace row for the same value, and that is correct: the workspace view groups on the UTM each session carries, so it has nothing to guard against; a link-keyed view would otherwise count a session against the very UTM that guessed the link.

The Detail Endpoint​

GET /api/analytics/campaigns/detail takes the same parameters plus a required value, and returns that value's daily trend, the links that carried it, and its split across the other UTM dimensions:

{
"groupBy": "campaign",
"value": "spring",
"byDate": [{ "date": "2026-09-11", "clicks": 0, "installs": 0, "sessions": 0, "conversions": 0 }, "…one entry per day in the window"],
"links": [{ "id": "…", "shortCode": "spring24", "title": "Spring launch", "clicks": 2, "installs": 1, "sessions": 2, "conversions": 1,
"totalRevenueByCurrency": [{ "currency": "USD", "amount": 40 }] }],
"breakdowns": { "source": [{ "value": "ig", "clicks": 2, "sessions": 2, "conversions": 1 }], "medium": [ "…" ] }
}
  • byDate is zero-filled and has exactly days entries, bucketed by calendar day (UTC). The totals in the table use a rolling window of hours, so the oldest day may hold slightly less than its share of the row.
  • links lists up to 25 links, ordered by revenue, then sessions, then clicks. A site session with no attributed link is counted in the table row but cannot be placed under a link here.
  • breakdowns holds the two UTM dimensions you did not group by, up to 10 values each. It is {} for groupBy=param, which has no other dimensions.
  • linkId and projectId scope it exactly as they scope the table, so a row and its detail cover the same traffic.

Grouping by a Passthrough Parameter​

groupBy=param groups by one of the passthrough parameters your links carry, rather than by a UTM value. Name the parameter with paramKey:

curl -H "Authorization: Bearer $LINKFORTY_API_KEY" \
"https://api.linkforty.com/api/analytics/campaigns?groupBy=param&paramKey=slug&days=30"

That answers "which slug performed best" across every link that carried one.

Two differences from the UTM groupings:

  • Clicks only. The web pixel records UTM values but not passthrough parameters, so sessions, pageViews, conversions and webRevenue are 0 on every row. The dashboard hides those columns in this mode.
  • paramKey is required. Omitting it returns 400, rather than grouping by everything.

A paramKey no click carried returns an empty row set, not an error.

Read the per-currency revenue fields

The response carries revenue, webRevenue, and totalRevenue for backwards compatibility, but they add amounts across currencies as though they shared a unit. Use revenueByCurrency, webRevenueByCurrency, and totalRevenueByCurrency instead.