SMS click-through
The SMS click-through page answers the channel-level question per-link analytics can’t: “what is the click-through rate on this campaign or queue?” It is the metric-definition page for SMS CTR — the per-link clicks and the channel-level rates are two different measures, and this page defines which denominator each rate uses so two reports never put divergent CTR numbers side by side. Per-link stats under Insights → Links show who clicked a given short URL; this panel rolls those rows up per campaign or messaging queue so SMS click-through compares side by side with the email channel’s Clicked metric. Everything the panel shows is a read-only rollup over the same short-link pipeline that mints tracked links on send, so it can never disagree with the per-link drill-down. For the deep walkthrough (reconciliation math, fixing the untracked share, email parity), see the SMS click-through parity guide; for where links get minted in the first place, see short links and click tracking.Where to reach it
The same click-through rollup ships in two places:- Insights → SMS click-throughs — the full panel: group-by control, window control, totals strip, and the bucket table described below.
- Messages → SMS messages — a compact summary embedded at the bottom of the SMS workspace. It shows the last 7 days as four figures — clicks, verified clicks, tracked links, and average clicks per link — with a day-by-day sparkline, plus a link through to Insights → Links for the per-link drill-down. The window is fixed at 7 days; use the Insights surface when you need a 24-hour or 30-day cut or per-bucket rows.
GET /api/v1/analytics/sms-click-through — see the SMS click-through API reference.
What the panel reads
For each campaign or queue in the selected window, the table reports:- Sends — outbound SMS in the window, whether or not they carried a tracked link.
- Tracked — sends that carried at least one tracked short link.
- Clicked — sends whose tracked links recorded at least one click.
- Clicks — raw clicks across the bucket’s links, including bot and link-preview traffic.
- Verified — clicks that passed the bot/link-preview filter.
- CTR — clicked sends divided by tracked sends. A bucket with no tracked sends shows
—, not a misleading zero. - Verified CTR — verified clicks divided by tracked sends. Use this when comparing against email’s bot-filtered Clicked metric.
Two clicks, two rates — link-shortener vs short-link CTR
SMS click-through on this panel counts one thing only: a recorded open event on a tracked short link. Per-link analytics under Insights → Links count open events per short URL — how many clicks onehttps://orb.to/xyz minted link recorded. The campaign click-through rate is the rollup the other way: across every tracked send in a bucket, how many sends recorded at least one click on any of the links they carried. A click on a URL that went out unshortened never reaches either surface — the raw http(s):// string ships untracked and contributes to the untracked share described under Reading the unattributed bucket.
Every CTR number on this page is send-count over send-count — the number of sends that got clicked over the number of sends that carried a tracked link. It is never “clicks divided by sends”: a single send can log many clicks on one link, and a clicker-in-many-bucket world can’t divide distinct clickers by sends. The per-link open-event counting (clicks per link, with the per-link verified filter) stays under Insights → Links; the per-send click-through rate lives here.
Denominator — engaged recipients, not delivered
CTR denominates over tracked sends, never over the whole send set and never over delivered. A plain-SMS-only bucket (no tracked links minted, anywhere in the window) is undefined — the panel and the API both returnnull rather than a misleading zero. Three sibling definitions keep the page honest:
- CTR — sends that recorded at least one click ÷ tracked sends. The raw rate; includes every click that landed, bot or human.
- Verified CTR — filtered (human-quality) clicks ÷ tracked sends. The cross-channel parity rate — the email Clicked metric is bot-filtered, so compare SMS against email on Verified CTR never on raw.
- Per-link open-event CTR — clicks per link ÷ tracked links on Insights → Links. This is a link-level rate, not the campaign click rate; the two surfaces are deliberately kept separate so campaign rollups and link drill-down agree bucket-for-bucket.
campaign_id, resolve the queue) before reading a spike; more on this under Reading the unattributed bucket below.
How the panel scopes
Two controls scope the numbers:- Group by: Campaign / Queue — the segmentation axis.
Campaignbuckets on thecampaign_idstamped at send time (per campaign);Queuebuckets on the messaging-service id recorded at send time, falling back to the provider tag when no queue resolves (per audience lane) — the same precedence the API’sgroupparameter uses. Per-link segmentation lives on Insights → Links; this control defines what a “bucket” is for every row on the panel. - Window: 24h / 7d / 30d — a trailing window, aligned to “now” at the render instant, for every number on the panel. Time windowing is not a rolling-n-calendar-days cut;
24hrolls back exactly 24 hours,7dand30dthe same in days. The totals strip always covers every bucket in the window (not just the page you’re viewing), and a shorter window is not a sub-sample of a longer one — it is a fresh rollup over a narrower span.
Pagination, sort, and empty states
Buckets sort by tracked sends descending (ties break on total sends), so the rows where click-through was actually measurable rank first. The ordering is fixed — column headers are not sortable. The table pages 25 buckets at a time. Page numbers appear only when the window has more than 25 buckets; the totals strip above the table still aggregates every bucket in the window, not just the page you’re viewing. Switching the group or window control resets the view to page 1. States the panel can sit in:- Loading — skeleton placeholders for the totals cards and table rows.
- No traffic — a “No tracked click-throughs yet” empty state with a link through to the Link tracking page. It appears when the window contains no sends (or none rendered any rows) — a new sender’s normal starting point, not an error.
- Load failure — a retryable error lane (“Couldn’t load the SMS click-through report”) with a retry button.
Reading the unattributed bucket
Theunattributed row collects sends where the scoped key never resolved — no campaign_id on the campaign grouping, no resolvable queue on the queue grouping. It is not an error grouping; it catches one-off sends, compose-dialog sends, and older payloads. A large unattributed share means attribution is lost upstream, and the fix is to stamp the key at send time — put campaign_id on the outbound payload, or resolve the queue on your messaging service.
Whether a send lands in Tracked at all is decided by the short-URL pipeline, in descending precedence:
metadata.shorten_urlson the send payload — explicit per-send opt-in; rewrites everyhttp(s)://URL in the body into a tracked link and stamps yourcampaign_idonto it.- SMS auto-shorten (tenant channel setting, default ON) — rewrites URLs longer than 30 characters, but only when the minted short URL is strictly shorter than the original.
- WhatsApp auto-track (tenant channel setting, default ON) — the WhatsApp counterpart, which also mints for scheme-less
www.spans.
Verified clicks and bot filtering
The Verified column applies the same predicate the per-link analytics use: a click crosses the verified bar only when its quality score reaches0.5 — user-agent present, and not a known bot or preview-fetch signature. Raw clicks stay in the store, so the filter lives at the read layer and retro-applies to already-collected data. Comparing raw CTR to email’s Clicked metric is apples-to-oranges — email’s number is already filtered, so use Verified CTR for cross-channel parity. For the scoring rules and why a missing user-agent scores below the bar, see short links and click tracking.
A rate is undefined when the bucket carried no tracked sends — the panel and the API return null rather than a fabricating zero, so a - or a null rate on one page never means “click performance was bad”; it means “this segment was never measurable.”
Why a spike can be scanner traffic
The click store keeps every recorded hit — bot or human — because raw counts are the only honest input the read layer can filter. Carrier SMS scanners, email gateway pre-fetchers, and link-security crawlers resolve short URLs the moment the message delivers, so a raw CTR spike looks like “this campaign took off” until Verified CTR filters the automated hits out. Read both columns side by side; the delta is scanner noise, not a false metric. Mitigation lives on the link-mint side, and it is a tenant-owned control set, not a platform gate:- Send fewer un-minted URLs — plain-text SMS sends where a raw
http(s)://URL never got minted to a short link contribute to Sends but nothing downstream, so scanner traffic only scales when you shorten more of what you send (auto-shorten and per-sendmetadata.shorten_urlsare the two opt-in routes). - Front the link with a recipient-only tap — the verified predicate drops clicks with no user-agent; a scanner that doesn’t present a UA re-scores as verified as soon as a real tap arrives. The dashboard page’s Verified column is the mitigation read-out — rely on Verified CTR wherever you compare against the email channel.
- Quarantine raw spikes — when raw and verified diverge per bucket, quarantine the spike onto the unattributed bucket first; a compounding-verified-only spike signals scanner follow-through, not audience engagement. For the scoring rules themselves (what counts as a known bot, the
0.5threshold), see short links and click tracking.
Troubleshooting
Defensive misreads
These are the reads that look wrong but are the panel working as designed — check them before chasing a data defect.- Email parity — read Verified CTR, not CTR. The email channel’s Clicked metric is bot-filtered. Lining it up against raw CTR mixes a filtered number with an unfiltered one and always flatters email. For cross-channel comparison use Verified CTR, which applies the same quality bar: a click crosses the verified line only when its quality score reaches the
0.5threshold — a user-agent is present and the click does not match a known bot or preview-fetch signature. —is informative, not missing data. A bucket with no tracked sends renders—for both CTR columns, and the API returnsnullfor the rate fields. A fabricated zero would tell you the campaign performed badly when in fact it was never measurable — the dash is the honest value.- Totals cover the whole window, not the visible page. The totals strip aggregates every bucket in the selected window even when the table paginates, so the totals won’t match a manual sum of the 25 rows on screen.
- The totals won’t foot to the per-link drill-down when untracked volume is large. Raw and verified counts only cover sends that carried tracked links. Sends whose URL never minted into a short link (30-character threshold, or a mint failure that shipped the original URL) contribute to Sends but nothing downstream — close the untracked share before reconciling against per-link analytics.
- Verified counts can rise without new clicks. The bot filter lives at the read layer, so when the known-bot catalogue expands, previously raw clicks re-score as verified on already-collected data. A week-over-week rise in Verified that raw doesn’t confirm is usually a filter update, not a traffic change.
API parity — how to read the metric
The full panel is backed byGET /api/v1/analytics/sms-click-through. group and window take the same values as the panel’s controls (campaign/queue, 24h/7d/30d), and the response carries the same paged rows and window-wide totals object. The embedded summary on the SMS messages page reads the same link rollup the Links insights endpoint serves. Details: SMS click-through API reference.
Read campaign-bucketed CTR (24-hour window)
Read queue-bucketed CTR (7-day window, page 2)
Response shape (one row)
GET /api/v1/links/insights; the click-through endpoint above returns campaign/queue buckets only. When a bucketKey resolves to unattributed, stamp campaign_id on the outbound payload (or fix the queue on the messaging service) — both moves are send-time decisions.
See also
- SMS click-through rollup walkthrough — the operator guide for this page’s tiles, controls, and misreads
- SMS click-through parity guide — reconciliation math, untracked volume, and email-parity reading
- Short links and click tracking — how links get minted, scored, and attributed
- SMS click-through API reference — the endpoint behind the panel