> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Reading the SMS click-through panel

> How to read and act on the SMS click-through panel — per-campaign and per-queue buckets, the 24h/7d/30d window, totals-versus-table reconciliation, the unattributed bucket, and the API equivalent.

# Reading the SMS click-through panel

The SMS click-through panel at **Insights → SMS click-throughs** answers the one question per-link analytics cannot: *what is the click-through rate on this campaign or queue, as a channel?* [Insights → Links](/guides/link-tracking-analytics) tells you who clicked a given short URL; this panel rolls every tracked link up per campaign or messaging queue, so SMS click-through sits next to the email channel's Clicked metric on equal terms.

The [SMS click-through page overview](/insights/sms-ctr) covers where the panel lives and what its columns mean. This guide is the reading guide — how to scope it, how to check it against itself, and how to push the leaky numbers toward zero.

## 1. What the panel shows

For each campaign or queue in the selected window, the table reports seven numbers:

* **Sends** — outbound SMS in the window, whether or not they carried a tracked link.
* **Tracked** — sends that carried at least one tracked short link. This is the engagement denominator: only tracked sends *can* produce a click.
* **Clicked** — sends whose tracked links recorded at least one click.
* **Clicks** — raw clicks across the bucket's links, including bot and link-preview traffic. This is a volume, not a rate.
* **Verified** — clicks that passed the verified-click predicate: the click crosses the verified bar only when its quality score reaches `0.5` — user-agent present, and not a known bot or preview-fetch signature.
* **CTR** — clicked sends ÷ tracked sends. A bucket with no tracked sends shows `—`, because the rate is undefined there, not zero.
* **Verified CTR** — verified clicks ÷ tracked sends. Use this one when comparing against email's Clicked metric, which is already bot-filtered.

Because the numerator divides by `tracked` rather than `sends`, a mostly-transactional bucket (few links minted) shows a small denominator and a high rate — that is correct, not inflated. Watch the untracked share (`sends − tracked`) instead: it is the volume that structurally cannot register engagement.

Everything on the panel is served by `GET /api/v1/analytics/sms-click-through` — the same read the [API reference](/api-reference/sms-click-through) documents, so the dashboard can never return a different story than the API.

## 2. Time window — 24 hours, 7 days, 30 days

The window control at the top of the panel sets the trailing aggregation window for every number on the page: **24h**, **7d** (the default), or **30d**. There is deliberately no 90-day option here; the schema-backed window is exactly these three values. For a longer trend line, pull the API on a schedule or use the wider 1–90 day range on the Links insights surface.

The compact summary embedded at the bottom of **Messages → SMS messages** is a different contract: its window is **fixed at 7 days**, showing clicks, verified clicks, tracked links, and average clicks per link with a day-by-day sparkline, plus a link through to **Insights → Links**. It is the quick health glance; the panel on **Insights → SMS click-throughs** is where you take a 24-hour pulse after a send wave or pull a 30-day read before a monthly review.

Both surfaces read the same short-link rollup, so choosing different windows changes *scope*, never *definition* — a verification predicate, a denominator rule, an unattributed-bucketing rule behaves identically on every window.

Switching the window restarts the table at page 1 (the old page index would slice rows that no longer exist), and the totals strip always covers **every** bucket in the window, not just the page on screen. Numbers refetch every 60 seconds behind the server-side cache, so a fresh send wave shows up within about a minute without a manual reload.

## 3. Group by — campaign or queue

Two groupings bucket every send:

* **Campaign** — buckets on the `campaign_id` stamped at send time. This is the cut a marketer wants: one row per campaign, so pre-flight reads ("did the May promo out-click the April promo?") answer straight from the table.
* **Queue** — buckets on the messaging-service id recorded at send time, falling back to the provider tag when no queue resolves. This is the cut an operator wants when triaging routing: a queue bucket with depressed CTR against healthy campaign buckets usually means traffic moved onto a queue whose recipients behave differently — a routing problem, not a creative problem.

Use the campaign cut for pre-flight and creative comparison; use the queue cut for routing and infrastructure triage. If the two cuts disagree in a specific direction, that disagreement is itself the diagnosis — campaign healthy + queue sick means routing moved, campaign sick + queue healthy means creative moved.

Sends where the scoped key never resolved — no `campaign_id` on the campaign grouping, no resolvable queue on the queue grouping — collect in the `unattributed` bucket (Section 5). Switching the grouping restarts the table at page 1, and buckets always sort by tracked sends descending so measurable rows rank first; column headers are not sortable.

## 4. Totals strip, bucket table, and reconciliation

The totals strip above the table carries the same five aggregates across **all** buckets in the window — total sends, sends with tracked links, sends clicked, overall CTR, overall verified CTR. It aggregates across pages, so a 25-per-page bucket table never changes what the strip reports.

Check a bucket against itself before acting on it:

```
CTR          = clicked   ÷ tracked
Verified CTR = verified  ÷ tracked
untracked    = sends     − tracked
```

If a bucket's printed rate does not reproduce from `clicked ÷ tracked`, the stale read is the 60-second cache, not the math — refetch. The deep reconciliation — reproducing the panel's totals from the raw sends and click sides — is worked end-to-end in the [SMS click-through parity guide](/guides/sms-click-through-parity).

The drill-down path for any movement on the table is **Insights → Links**: scope the per-link surface by `campaign_id` and the same window, and the rows here decompose into the exact URLs behind the number — which link dragged the rate, and whether the raw-versus-verified gap says it is real engagement loss or preview-fetch noise. Because both surfaces read the same pipeline, the rollup can never disagree with its own drill-down; if the numbers look inconsistent between the two pages, the window or grouping scope is different, not the data.

## 5. The unattributed bucket — pushing it toward zero

The `unattributed` row is a catch-all, not an error grouping: it collects sends where the attribution key never resolved. Three causes account for almost all of it:

1. **Un-tracked sender links.** A send whose URLs never went through the mint pipeline carries nothing to attribute on. The per-send `metadata.shorten_urls` opt-in and the tenant SMS/WhatsApp auto-shorten settings control minting; both are covered in [short links and click tracking](/guides/short-links-and-click-tracking).
2. **Cascade or plain-variant sends.** When a campaign runs variants and a fallback plain template (or a cascade hop) goes out without the campaign's key on the payload, the send lands under `unattributed` even though a human would call it part of the campaign.
3. **A template missing the attribution token at send time.** If the payload never carried `campaign_id` (campaign grouping) or never resolved a messaging-service id (queue grouping), the bucket forms at send time — no panel setting can re-attribute it after the fact.

The fix is always upstream: stamp `campaign_id` on the outbound payload, or resolve the queue on the messaging service. The [SMS click-through parity guide](/guides/sms-click-through-parity) walks the bucket-fix in detail.

Pushing `unattributed` toward zero is an attribution-hygiene job, and its size on the rollout of a new campaign template is the early-warning signal — a rising `unattributed` share after a template change means the template stopped stamping its key.

## 6. API equivalent

Every tile on the page maps to one endpoint — `GET /api/v1/analytics/sms-click-through` — with `group` and `window` taking the same values the panel's controls expose, plus `page` and `pageSize` (default 25, max 100):

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/analytics/sms-click-through?group=campaign&window=30d" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The response returns the paged `rows` (one object per bucket with `bucketKey`, `sends`, `tracked_sends`, `sends_with_clicks`, `total_clicks`, `unique_clickers`, `verified_clicks`, `click_rate`, `verified_click_rate`), the window-wide `totals` object, and `pagination` — under the standard `{ data, meta }` envelope. Rate fields come back `null` when the bucket carried no tracked sends, and unattributed sends group under a `null` `bucketKey`. Responses cache for 60 seconds server-side.

For the full request/response schema, worked per-queue and per-campaign examples, and SDK forms, see the [SMS click-through API reference](/api-reference/sms-click-through).

## 7. Who can see it

The panel follows the standard Insights hub visibility rule: it renders for every member who can reach the **Insights** section — owner, admin, developer, and viewer seats all read it — and there is no SMS-click-through-specific role gate. The tile hide on the Insights hub grid is presentation-only; the destination page re-checks the same normalized role server-side, and reading it succeeds for any seat the hub shows it to. The [Insights hub orientation](/insights/overview) page maps each tile to its read gate.

## Troubleshooting

* **A bucket shows `—` for CTR.** The bucket carried no tracked sends; the rate is undefined there and both the panel and the API return `null` rather than a fabricated zero. Untracked volume (`sends − tracked`) is visible per bucket — if that share is high, shorten more of what you send.
* **Unattributed is the biggest row.** Stamp `campaign_id` on campaign sends, or fix queue resolution on the messaging service — both are send-time decisions, not panel settings (Section 5).
* **Raw and verified disagree.** Comparing raw CTR to email's Clicked metric mixes a filtered number with an unfiltered one; line raw against raw elsewhere, or use Verified CTR for parity with email.
* **The Messages → SMS summary shows a flat zero.** Its window is fixed at 7 days — clicks older than a week fall out of scope. Open the Insights surface for the 24h/7d/30d window control and per-bucket rows.
* **CTR moved but nothing about the campaign changed.** Check delivery health first — CTR's denominator is tracked sends, not delivered messages, so a delivery dip inside the window depresses the rate before any creative change is suspect. Start from [SMS destination blocks](/troubleshooting/sms-destination-blocks) for delivery-side symptoms, and the [voice channel guide](/channels/voice) if the movement coincides with an omnichannel wave.

## See also

* [SMS click-through panel overview](/insights/sms-ctr) — what the panel is, where it lives, and its columns
* [SMS click-through parity guide](/guides/sms-click-through-parity) — reconciliation math, mint switches, and the unattributed-bucket fix in depth
* [Reading the SMS click-through rollup](/guides/insights-sms-ctr) — the operator walkthrough of tile-by-tile behavior and common misreads
* [Reading link tracking analytics](/guides/link-tracking-analytics) — the per-link drill-down this rollup sits on top of
* [SMS click-through API reference](/api-reference/sms-click-through) — the endpoint behind the panel
