Campaign analytics: reading the /campaigns/[id] panels
Open any campaign under Campaigns and the detail page stacks a set of analytics panels: a delivery stats strip, then, depending on how the campaign is configured, a drip funnel, A/B results, holdout lift, ROAS and attribution funnel, link tracking, optimize and fallback chain, governance, and the per-recipient table. This page walks each panel in the order the page renders it: the question it answers, the columns it shows, the API read behind it, what its empty state means, and who can read it. This is the campaign console guide. The SMS click-through panel on the Insights side keeps its own pages: SMS click-through panel overview and Reading the SMS click-through rollup. Where a campaign panel and an Insights panel show the same underlying number, the reconciliation section explains why they agree. Every panel follows the same loading contract: a skeleton while the read is in flight, an explicit error with a Retry button when the read fails, and silence when there is no signal yet. Panels never render a misleading all-zero card before the campaign has anything to report, so a missing panel is itself information: the campaign has not produced that signal yet. While a campaign is in a live status the page re-polls its reads every few seconds; once the campaign reaches a terminal status (completed, paused, cancelled, failed) polling stops and the numbers freeze at their final values. A frozen total on a finished campaign is correct, not stale.1. The stats strip
Question it answers: did the send actually reach people? The strip at the top of the page totals the campaign’s lifetime delivery: Sent, Delivered, and Failed, plus, where the channel can measure it, Opened, Clicked, and Replied. There is deliberately no window picker here; the strip covers the campaign from first send to now. If you want a trailing window (24h/7d/30d) over the same events, use the Insights surfaces instead.- API read:
GET /campaigns/:id/stats, alongside the campaign record itself. - Refresh: re-polled every few seconds while the campaign is live; frozen once terminal.
- Empty state: zeros before the first send goes out. That is the expected pre-launch read, not an error.
- Who can read it: every seat that can open the campaign. Mutating controls elsewhere on the page (pause, approve, pick a winner) carry their own permission checks; the analytics reads do not.
2. Drip funnel
Question it answers: how are recipients moving through the drip’s steps, and where do they drop off? Rendered for drip campaigns only. A summary row shows Enrolled, Active, Completed, and Dropped / failed counts; below it, one row per step shows how many recipients reached the step, how many messages were delivered, failed, or dropped, and the step’s drop-off rate. The retention bar on each row is the share of the original audience that still reaches that step, so the funnel visibly narrows as recipients fall out down the sequence.- API read:
GET /campaigns/:id/drip-analytics. - Empty state: the panel renders nothing until at least one recipient has reached a step. Right after launch, before the first waits expire, its absence is normal.
- Reading tip: a high drop-off on one step points at that step’s content or timing, not at the audience; compare it against the previous step’s rate before touching the template.
3. A/B test editor and results
Question it answers: which variant wins, and by how much? When the campaign carries an A/B configuration, the editor shows the variants, their traffic split, and the auto-winner settings. The results chart then tracks each variant’s delivery and conversion once sends begin, so the gap between variants reads at a glance. The auto-winner option applies the leading variant automatically once the run meets the criteria you configured (sample size and confidence), so a live campaign does not wait on a human to declare a winner; with auto-winner off, you pick one manually, which is the same action the API exposes asPOST /campaigns/:id/ab-select-winner.
- API reads:
GET /campaigns/:id/ab-resultsfor the chart; variant generation is available atPOST /campaigns/:id/generate-variants. - Empty state: no A/B configuration means the editor and chart do not render; configured but unsent means the chart waits for the first sends.
- Who can read it: anyone who can open the campaign; selecting a winner requires campaign edit rights.
- In depth: A/B testing campaigns covers designing the split and interpreting significance.
4. Holdout lift
Question it answers: did sending this campaign lift conversion above a do-nothing baseline? Rendered when the campaign was launched with a holdout (control) cohort. Three tiles compare Treatment conversion against Control (baseline) conversion and show the incremental lift between them, with a statistically significant badge when the effect clears the significance test, plus the 95% confidence interval and p-value. This is the only panel that can separate “the campaign worked” from “these contacts would have converted anyway”, which a per-variant A/B winner cannot answer.- API read:
GET /campaigns/:id/holdout-lift. - Empty state: no holdout configured at creation means the panel never renders. If the control cohort’s conversion is not yet measurable, the panel says lift is pending while confirming the control group is being held out correctly.
- In depth: the mechanics of control cohorts and lift math live in Measuring campaign holdout uplift; this page covers reading the panel only.
5. ROAS and the attribution funnel
Question they answer: did the spend pay back, and how did the audience move from touched to paying? The two panels are complementary. The ROAS panel answers the model question: under an attribution model (last touch, linear, or time decay, selectable on the panel), how much revenue does this campaign deserve credit for? It shows the ROAS ratio with the matching ROI, attributed revenue with the count of converting contacts, send cost in your reporting currency, and tracked clicks, then splits revenue credit across the contributing campaigns that touched the same converters and, one level deeper, a per-creative table (revenue, spend, ROAS, share) grouped by template or by message. Revenue recorded in a currency with no available exchange rate is surfaced in a notice rather than silently dropped. The attribution funnel answers the cohort question without a model: how this campaign’s own audience moved through Reached → Clicked → Converted → Ordered, with the drop-off at each step, and what the converted cohort is worth (gross revenue, the send cost behind it, and the resulting ROAS), plus the top converting contacts.- API reads:
GET /campaigns/:id/roasandGET /campaigns/:id/roas/touchpointsfor the panel and its per-creative table;GET /campaigns/:id/attribution-funnelfor the funnel. - Empty state: both panels stay hidden until the campaign has sends and, for revenue figures, conversions. A No send cost recorded dash under spend means cost data has not landed for this campaign yet.
- In depth: attribution windows, models, and revenue import are covered in Campaign ROAS and attribution; export of the ROI summary in ROI summary export.
6. Link tracking
Question it answers: for this campaign, which links were clicked, how often, and by how many people? Three stat cards show Clicks (with the verified-human subset as the subtitle), Unique clickers, and Tracked links with average clicks per link. A per-day bar chart shows click volume over the window, and the table lists each link’s destination, short URL, clicks, verified clicks, and unique visitors. Verified clicks exclude bot and link-preview traffic, so a bot-heavy blast cannot read as engagement.- API read:
GET /links/insights?campaign_id=:id, the same pipeline that powers the tenant-wide link dashboard. - Empty state: “No tracked links for this campaign” means the send carried no tracked short links, almost always because URL shortening was off when the message went out. The panel tells you which setting to enable for the next send; it cannot retrofit tracking onto links already sent.
- Where it reconciles: Insights → Links is the tenant-wide per-link view; the SMS click-through panel rolls the same clicks up per campaign as a rate. This panel is the campaign-scoped drill-down between the two: the click totals here are the raw material the other two surfaces aggregate, so scoping them to the same campaign and window reproduces the same numbers.
7. Optimize and the fallback chain
What they do: one recommends, the other explains a configured behavior. The AI optimization panel is on-demand; suggestions are generated when you press Optimize (POST /campaigns/:id/optimize), never automatically. It returns a suggested send time and alternative message variants, each with its SMS segment count and a copy button for pasting into the template, and it discloses how the suggestion was produced: the model (or an explicit “heuristic, no AI model”), the confidence percentage, and the estimated token cost. Suggestions are advisory and change nothing until you copy one into the template. Delivery-rate prediction deliberately lives in exactly one place, the Deliverability Lab in the campaign composer, so the two surfaces never show competing predictions.
The budget-aware fallback panel is a read-only view of the chain configured at creation: the primary channel, then each fallback channel with its trigger (for example “after 6h not delivered” or “no engagement”), ending in the last-attempt channel that has no further fallback. When the campaign carries a credit cap, the panel shows it as the spend guardrail that auto-pauses the campaign at that amount. The panel renders nothing for single-channel campaigns, because there is no downgrade path to show. It displays the configuration; the scheduler, not this panel, advances recipients along the chain.
8. Governance
Question it answers: is anything gating this campaign, and who has signed off? The governance panel shows the campaign’s approval state and the tenant gates that apply to it, such as quiet hours and channel readiness, in one place, and exposes the approve and reject actions to seats that hold them.- API reads:
GET /campaigns/:id/governance-summary; the actions arePOST /campaigns/:id/approveandPOST /campaigns/:id/reject. - Empty state: campaigns in an organization without an approval flow simply show no pending-approval state; gates that do not apply are not listed as failing.
- Who can read it: everyone who can open the campaign; acting on approval requires the approval permission.
9. Recipients
Question it answers: what happened to each individual recipient? The recipients table lists one row per contact in the audience with that contact’s current send status (queued, sent, delivered, failed, and the channel-specific outcomes), so a single row answers “did this person get it?” The list is virtualized and loads in pages as you scroll; when the inner list reaches its edge, scrolling hands off to the page so you never get stuck inside the table.- API read:
GET /campaigns/:id/recipients. - Empty state: before launch the table lists the resolved audience with no delivery outcomes yet; an audience that resolved to zero addressable contacts is reported as a failure reason in the pause banner, not as an empty table.
10. The pause banner and reason dialog
Question it answers: who stopped this campaign, and what now? A paused campaign shows whether the pause was manual (a teammate pressed pause,POST /campaigns/:id/pause, which records no reason) or automatic (the platform stopped it). Automatic pauses always carry a human-readable explanation with a recovery hint, for example:
- WhatsApp quality RED: Meta lowered the template’s quality rating; resolve it in WhatsApp Manager and resume once the rating recovers.
- Credit cap reached: the campaign spent up to its guardrail; raise the cap or review spend, then resume.
- Sending limit hit: the rate cap was reached before every recipient was sent; wait for the reset or raise the cap, then resume or clone to finish.
- Every recipient failed: usually a sender or address problem; fix the setup and clone the campaign to retry.
- No progress for over two hours: a mid-send interruption ended the campaign automatically; some recipients may already have received it, so clone to finish sending to the rest.
Reconciling with Insights and the API
Three surfaces can show the “same” campaign numbers: this page, Insights → Analytics (realtime reading guide), and the aggregateGET /analytics reads your integration makes. They agree by construction when you compare like for like, because they read the same send, delivery, and click pipeline. When two numbers look different, check scope before suspecting data:
- Window. The campaign page reports the campaign’s lifetime; Insights reports the window you selected. A 7-day Insights window and a lifetime campaign total are different questions.
- Granularity. The campaign page attributes to one campaign; Insights rolls up across campaigns, queues, or channels. The SMS click-through panel’s campaign grouping is the bridge between the two.
- Lag. The campaign page re-polls every few seconds while live; Insights reads carry a short ingestion and cache lag. A send wave can show on the campaign page a few seconds before it lands in Insights. For a finished campaign both sides settle to identical totals.
Troubleshooting
- Stats look stale on a finished campaign. Expected: polling stops at terminal status and the totals freeze. Compare against Insights with a window that covers the send, not against a live refresh.
- Zeros while the campaign is running. Delivery reports lag sending by seconds to minutes per provider; the next poll picks them up. If zeros persist, check the pause banner for an automatic pause and the governance panel for a gate.
- The link tracking panel is empty. The send carried no tracked short links because URL shortening was off at send time. The same sends also land in the SMS click-through panel’s unattributed bucket; short links and click tracking covers enabling minting for future sends.
- No holdout lift panel. The holdout percentage is set at creation; a campaign launched without one has no control cohort and therefore no lift. Measuring campaign holdout uplift covers configuring it on the next campaign.
- ROAS shows dashes. No attributed conversions yet, or no send cost recorded. Check that revenue events are flowing in and that the attribution window covers the conversions; Campaign ROAS and attribution walks the setup.
- No drip funnel. No recipient has reached a step yet; the panel appears with the first reaches.
- A/B chart shows no winner yet. The run has not met the configured sample size; with auto-winner on, the winner is applied automatically once it does.
See also
- Campaigns overview — the five-bullet tour of the campaigns console this page deepens.
- Campaign end-to-end — building, launching, and reading a campaign in one pass.
- A/B testing campaigns — designing splits and interpreting the results chart.
- Measuring campaign holdout uplift — control cohorts and lift math.
- Campaign ROAS and attribution — models, windows, and revenue import.
- Reading link tracking analytics — the tenant-wide per-link view.
- SMS click-through panel overview — the campaign-bucket click-through rate this page’s link panel feeds.
- Insights analytics realtime — the Insights side of the reconciliation above.