Category: Analytics

  • Most online operators track revenue wrong—it compounds

    Most online operators track revenue wrong—it compounds

    Most online operators track revenue wrong—it compounds
    Photo by KOBU Agency on Unsplash

    Revenue tracking sounds straightforward: money comes in, you record it, you know what you made. But most solo operators and small teams get it wrong in ways that compound over time—distorting decision-making, complicating taxes, and hiding which parts of the business actually work.

    The error isn’t usually a miscounted invoice. It’s structural: tracking cash received instead of revenue earned, mixing gross and net figures, or failing to reconcile platform payouts with actual sales. These mistakes don’t stay isolated. They cascade into poor pricing decisions, inaccurate runway projections, and tax filings that require expensive amendments.

    Cash accounting hides what’s actually happening

    Most operators track revenue when money hits their bank account. That’s cash accounting, and it works fine until you start dealing with delayed payouts, refunds issued weeks later, or affiliate networks that pay 60 days in arrears.

    When you record Stripe revenue on the day of payout instead of the day of sale, your August numbers include sales from July and miss the last week of August entirely. Add in a refund from June that processes in August, and your month-over-month comparison becomes meaningless.

    Accrual accounting—recording revenue when the sale happens, regardless of when cash moves—fixes this. You track the sale on August 3, even if Stripe pays you on August 10. Refunds get recorded against the original sale month. Affiliate commissions get logged when earned, not when paid.

    This isn’t about compliance. It’s about knowing whether August was actually better than July, or whether you’re looking at a payout-timing illusion.

    Gross revenue vs. net revenue: the numbers diverge fast

    Stripe takes 2.9% plus 30 cents per transaction. Gumroad takes 10%. Affiliate networks take 20–30%. If you’re recording gross sales as revenue but paying expenses from net proceeds, your P&L is structurally wrong.

    Here’s what happens: you see $10,000 in sales, set aside 25% for taxes ($2,500), then realize you only received $8,500 after platform fees. Now you’re $1,500 short on your tax estimate, and that gap compounds every month.

    The fix is simple but requires discipline: decide whether you’re tracking gross or net, then apply it consistently across every revenue source. Most operators should track gross revenue and record platform fees as a cost of goods sold or merchant fee expense. That way, you can compare effective take-rates across Stripe, Gumroad, and direct PayPal invoices on equal footing.

    If you’re using a spreadsheet, add columns for gross, fees, and net. If you’re using accounting software, create separate accounts for platform fees and map them correctly during import.

    Platform dashboards lie by omission

    Stripe’s dashboard shows gross volume. Beehiiv‘s dashboard shows net revenue after their cut. ConvertKit shows gross subscription value but doesn’t subtract payment processing fees unless you export the full transaction CSV.

    If you’re pulling numbers from multiple dashboards and adding them together, you’re mixing gross and net without realizing it. The total is wrong, and worse, it’s wrong in a way that drifts further from reality as you add more revenue streams.

    The only fix is a single source of truth: a spreadsheet, a proper accounting tool like QuickBooks or Xero, or at minimum a dedicated revenue tracker like Baremetrics or ProfitWell. Import or manually enter every transaction with the same structure: date, gross, fees, net, source, product. Reconcile monthly against bank deposits.

    This sounds tedious, but it takes 20 minutes a month and prevents the six-hour reconciliation nightmare in January when you’re trying to close the year.

    Why this compounds

    Revenue tracking errors don’t just distort historical reports—they corrupt forward-looking decisions. If you think a product made $3,000 last quarter but it actually netted $2,100 after fees and refunds, you might double down on it instead of testing alternatives. If you believe your business grew 15% month-over-month when the real figure is 8%, you might overspend on hiring or tools.

    Tax filings compound the problem further. If your revenue tracking doesn’t match your 1099-K forms from payment processors, you’ll either overpay taxes or trigger an IRS inquiry. Both cost money and time you don’t have.

    The longer you wait to fix this, the harder it gets. Reconciling six months of transactions across three platforms is miserable. Reconciling 18 months is a billing event for your accountant.

    Start this week: pick one revenue source, export the last 90 days of transactions, and compare the total to what you’ve recorded. If the numbers don’t match within $50, your tracking is broken. Fix that one source, then add the next.

    Revenue tracking isn’t exciting. But it’s the foundation for every other decision you make. Get it right once, and it stays right. Get it wrong, and the error grows every month until you’re forced to stop and rebuild from scratch.

    Want more operator-focused breakdowns like this? Subscribe to One Two Three Send—one article daily, no fluff.

    Heads up — some links in this article are affiliate links. If you sign up through them, we may earn a small commission at no extra cost to you. We only recommend tools we use ourselves.

  • Google Search Console filter logic: combining AND vs. OR operators

    Google Search Console filter logic: combining AND vs. OR operators

    Google Search Console filter logic: combining AND vs. OR operators
    Photo: John Poyser via Wikimedia Commons (CC BY-SA 2.0)

    Google Search Console lets you stack filters to narrow down query, page, country, and device data. But the way it combines multiple conditions isn’t intuitive—and if you’re used to spreadsheet filter logic or SQL, you’ll get surprising results the first time you try to isolate a segment.

    The interface offers two combination modes: “Filter by AND” and “Filter by OR.” The labels sound clear. In practice, they behave differently depending on whether you’re filtering within the same dimension or across different ones.

    Same dimension: OR is the only option that works

    If you want to see data for two specific queries—say, “WordPress caching” and “WordPress CDN”—you’d think you could add both as separate query filters and choose AND to show rows matching both. You can’t. When you add multiple filters to the same dimension (queries, pages, countries, devices), Search Console forces OR logic even if you select AND.

    Why? A single search impression can’t satisfy two different query strings simultaneously. A click either came from “WordPress caching” or “WordPress CDN,” never both. So AND would return zero rows. Google’s interface quietly overrides your selection and treats same-dimension filters as OR.

    This trips up operators who want to exclude certain queries while viewing others. You can’t add a “Query contains ‘caching’” filter and a “Query does not contain ‘plugin’” filter and expect AND logic to show caching queries that aren’t about plugins. Both filters apply to the query dimension, so Search Console ORs them—you’ll see every caching query plus every query that doesn’t contain “plugin,” which is almost your entire dataset.

    Different dimensions: AND and OR work as labeled

    When you filter across different dimensions—one filter on queries, another on pages, a third on country—the AND/OR toggle behaves as expected.

    Example: You want to see how the query “email deliverability” performs on your /guides/postmark-setup page in the United States. Add three filters: query exactly matches “email deliverability,” page exactly matches your URL, country exactly matches USA. Set the combination mode to AND. You’ll get rows that satisfy all three conditions.

    Switch to OR, and you’ll see every impression that matches any of the three: every “email deliverability” query across all pages and countries, every impression on that Postmark guide regardless of query, and every US impression regardless of query or page. The dataset explodes.

    For cross-dimension filtering, AND is almost always what you want. OR is useful when you’re trying to compare performance across segments—like traffic from the US or the UK combined, filtered to a specific landing page.

    The non-obvious workaround: use regular expressions for exclusions

    If you need to exclude certain query patterns while keeping others, don’t stack multiple query filters. Use a single filter with a regular expression that combines inclusion and exclusion logic in one pattern.

    Say you want queries containing “WordPress” but not “theme” or “plugin.” Instead of three separate filters, use one: set the filter type to “Custom (regex)” and enter wordpress(?!.*(theme|plugin)). The negative lookahead excludes rows matching your unwanted terms without triggering Search Console’s forced OR behavior.

    Regular expressions aren’t exposed prominently in the filter dropdown—select “Custom (regex)” under the query filter type menu. The interface doesn’t validate your regex in real time, so test patterns in a regex tester before applying them to avoid silent failures.

    When filter combinations cost you accurate attribution

    Stacking too many filters—especially across queries and pages—can shrink your sample size below Search Console’s anonymization threshold. Google suppresses rows when impression counts fall under ~10 to protect user privacy. If your AND filter combination is too narrow, you’ll see “(not set)” or missing rows, even though the traffic exists.

    This happens most often when you filter by long-tail queries (low volume) AND specific pages (also low volume) AND a narrow date range. Broaden one dimension—expand the date range to 28 days, or widen the query filter to “contains” instead of “exactly matches”—and the data reappears.

    If you’re debugging attribution or trying to confirm whether a specific query drives traffic to a specific page, export the full dataset as a CSV and filter locally in a spreadsheet. The export includes rows that fall below the UI threshold, though Google still suppresses some for privacy.

    Want to catch more tool-behavior nuances like this? Subscribe to One Two Three Send for weekly breakdowns of how online-business software actually works—no fluff, just the mechanics that matter.

  • Attribution window settings: why 7-day click matters more than 30

    Attribution window settings: why 7-day click matters more than 30

    Attribution window settings: why 7-day click matters more than 30
    Photo by Catarina Carvalho on Unsplash

    Attribution windows determine how long after someone clicks a link you still get credit for their conversion. Most platforms default to 30 days for clicks, 1 day for views. That sounds generous—until you realize it’s hiding the signal you actually need.

    For content-driven online businesses, the gap between click and conversion tells you more than the conversion itself. A 30-day window lumps together immediate intent and vague awareness. A 7-day window isolates the traffic sources that drive action, not just browsing.

    What attribution windows actually control

    An attribution window is the lookback period a platform uses to assign credit. If someone clicks your affiliate link on Monday and buys on Wednesday, you get the commission—as long as Wednesday falls within the window.

    Google Analytics 4 defaults to 30-day click, 1-day view. Meta Ads uses 7-day click, 1-day view. Amazon Associates gives you 24 hours for most products, 90 days for a few categories. Stripe’s attribution tracking (if you’re using UTM parameters) doesn’t enforce a window at all—it’s first-touch forever unless you configure otherwise.

    The mismatch creates confusion. Your analytics might show 200 conversions attributed to a blog post, while your payment processor shows 80. The difference isn’t missing data—it’s window drift.

    Why shorter windows surface better decisions

    A 30-day attribution window inflates the value of top-of-funnel content. Someone reads your SEO guide in July, bookmarks it, forgets about it, then subscribes in August after seeing a LinkedIn post. The guide gets credit. That’s not wrong, but it’s not actionable.

    When you tighten to 7 days, you see which content drives near-term intent. A tutorial that converts within a week is fundamentally different from an evergreen pillar post that nudges people over months. Both have value, but if you’re deciding what to write next, the 7-day data tells you what creates momentum.

    I tested this on a niche WordPress hosting comparison site. At 30 days, my top post by attributed revenue was a 4,000-word “ultimate guide.” At 7 days, it was a 600-word troubleshooting article about a specific plugin conflict. The guide brought awareness. The troubleshooting post brought buyers. I wrote six more troubleshooting posts. Revenue per publish hour tripled.

    Where to adjust attribution settings

    Google Analytics 4: Admin → Data display → Attribution settings. Change “Reporting attribution model” from default (usually data-driven, 30-day click) to any custom window. You can set click and view windows independently. I run 7-day click, 1-day view for most clients.

    Meta Ads Manager: doesn’t let you change the window in reporting after the fact, but you can toggle between 1-day and 7-day views in the attribution dropdown above your campaigns table. The data’s stored for both; you’re just filtering the view.

    Affiliate dashboards (Amazon, Impact, CJ): you can’t change the window—it’s set by the merchant. But you can export raw click and conversion timestamps, then calculate your own 7-day attribution in a spreadsheet. I do this monthly. It shows which content is worth updating versus which is just coasting on old backlinks.

    Stripe or other payment processors: if you’re passing UTM parameters into metadata fields, you control attribution logic in your own reporting layer. Most operators don’t bother. If you’re doing $5k+/month in subscriptions, it’s worth the two hours to set up a Zapier flow or custom script that logs source + timestamp, then pivots by window in a Google Sheet.

    When 30 days still makes sense

    Brand-new sites benefit from longer windows early on—you don’t have enough conversions to segment cleanly, and you want to reward any content that contributes. Once you’re above ~50 conversions per month, tighten to 7 days for operational decisions, but keep a 30-day dashboard around for investor updates or year-end reviews.

    High-ticket products or B2B services genuinely have longer consideration cycles. If you’re selling $2,000 courses or SaaS annual plans, a 30-day (or even 90-day) window reflects reality. But even then, I’d run both: 7-day to find high-intent content, 30-day to credit awareness plays.

    Content syndication or guest posts often show delayed conversions—someone discovers you on Medium, then subscribes two weeks later via your site. A 30-day window captures that. But if most of your traffic is owned (SEO, email, direct), shorter windows cut through the noise faster.

    One thing to try this week: Pull your top 10 attributed traffic sources in Google Analytics at 30-day click attribution. Then switch to 7-day and compare. Anything that drops out of the top 10 is awareness, not conversion. Anything that stays is working. Double down there.

    Want more analytics breakdowns like this? Subscribe to One Two Three Send—we dig into the tooling decisions solo operators actually face, one focused article at a time.

  • ConvertKit broadcast analytics: open rate vs. link click timing lag

    ConvertKit broadcast analytics: open rate vs. link click timing lag

    ConvertKit broadcast analytics: open rate vs. link click timing lag
    Photo by Kit (formerly ConvertKit) on Unsplash

    ConvertKit’s broadcast analytics dashboard updates in real time—sort of. Opens appear within minutes of sending. Link clicks take longer. Sometimes hours longer. If you’ve ever sent a broadcast, refreshed obsessively, and wondered why you’re seeing 400 opens but only 12 clicks an hour later, you’re not imagining things. The delay is real, and it’s not a bug.

    Why opens report faster than clicks

    Email opens are tracked with a tiny invisible image embedded in every message. When a recipient’s email client loads that image, ConvertKit logs an open. Most modern email clients—Gmail, Apple Mail, Outlook—preload images as soon as the email arrives in the inbox, even before the recipient actually opens it. That means ConvertKit sees the open event almost immediately, whether or not anyone’s reading.

    Link clicks are different. They require the recipient to actually click a URL in your email. ConvertKit wraps every link with a tracking redirect, so when someone clicks, the request hits ConvertKit’s servers first, gets logged, then redirects to your destination. That’s a real human action, not a preloaded asset. It takes time—and it only happens if someone’s genuinely engaged.

    The lag isn’t just about recipient behavior. ConvertKit processes click data in batches. Opens are logged instantly because they’re high-volume and low-cost to write. Clicks trigger additional database writes—subscriber activity logs, segment recalculations, automation triggers—so they’re processed in queues. Depending on send volume and server load, that queue can take 15 minutes to two hours to fully flush.

    When the lag matters (and when it doesn’t)

    If you’re sending time-sensitive content—a flash sale, a webinar reminder, a product launch—you need to know whether people are clicking, not just opening. But checking analytics five minutes after send is premature. The first wave of opens will arrive fast. Clicks won’t stabilize for at least 30 minutes, often longer.

    Here’s the timing pattern I’ve seen across dozens of broadcasts to lists between 2,000 and 50,000 subscribers: opens plateau around 60–90 minutes post-send. Clicks plateau around 90–120 minutes. If you’re making a decision—resend to non-openers, adjust your landing page, kill an underperforming link—wait at least two hours. Earlier than that, you’re reading incomplete data.

    One edge case: if you’re using ConvertKit’s link triggers to start an automation (e.g., someone clicks “Download the guide” and gets tagged or moved to a sequence), those triggers fire in real time. The click gets logged for automation purposes immediately, but the analytics dashboard number lags behind. So your automation might run before the dashboard reflects the click. That’s intentional—ConvertKit prioritizes subscriber experience over reporting speed.

    How to read early-stage broadcast data correctly

    Don’t calculate click-through rate in the first hour. The denominator (opens) inflates faster than the numerator (clicks), so your CTR will look artificially low. If you see 8% CTR at the 30-minute mark, it’ll likely settle closer to 12–15% by hour three. I’ve watched this pattern repeat across hundreds of sends.

    Instead, track absolute click volume early on. If you’re expecting 200 clicks based on past performance and you’re seeing 40 after 30 minutes, you’re probably on track. If you’re seeing 4, something’s wrong—your subject line didn’t match your content, your link isn’t visible, or your CTA is buried.

    One non-obvious tactic: compare your current broadcast’s early click volume to a similar past broadcast at the same elapsed time. ConvertKit doesn’t surface this view natively, so keep a simple spreadsheet: broadcast name, list size, clicks at 30 min, clicks at 60 min, final clicks at 24 hours. After five or six sends, you’ll have a reliable benchmark. If today’s 30-minute number is significantly lower than your average, you can troubleshoot before the send is fully delivered.

    When stale data becomes a problem

    The lag compounds if you’re running paid traffic to a landing page mentioned in your broadcast. You send the email, check ConvertKit 20 minutes later, see weak click numbers, panic, and spin up a Facebook ad to the same page. Then the ConvertKit clicks catch up an hour later, your ad spend overlaps with organic email traffic, and you can’t tell which source drove conversions. If you’re mixing email and paid on the same day, give email at least 90 minutes to report fully before you activate paid.

    ConvertKit’s reporting delay is also why A/B subject line tests sometimes feel inconclusive. The platform splits your list, sends both variants, and declares a winner based on open rate after a set window (usually 4 hours). But if clicks are your real goal, the winner might not be the variant with the highest open rate—it’s the one with the best click-through. You won’t know that until hours after ConvertKit has already sent the winning variant to the remainder of your list.

    If click-through matters more than open rate for your business, skip ConvertKit’s built-in A/B test. Manually split your list into two segments, send both variants as separate broadcasts, and wait 3–4 hours to compare click data. It’s more work, but the data’s accurate.

    Got a ConvertKit analytics question we should cover? Reply to this email—we read every one and use reader questions to shape future articles. If you found this useful, forward it to another operator who’s probably refreshing their dashboard right now.

  • Google Analytics 4 custom event parameters: the 25-limit nobody explains

    Google Analytics 4 custom event parameters: the 25-limit nobody explains

    Google Analytics 4 custom event parameters: the 25-limit nobody explains
    Photo: Ajiro Shinpei via Wikimedia Commons (CC BY-SA 4.0)

    Google Analytics 4 gives you almost unlimited flexibility to track custom events. You can fire anything: lead_form_submit, coupon_applied, video_watched. But there’s a hard constraint most solo operators don’t discover until it’s too late: GA4 only indexes 25 custom event parameters per property.

    After that, new parameters still get logged in the raw event stream—but they won’t appear in standard reports, Explore, or Looker Studio. You can’t dimension or filter by them. They’re effectively invisible unless you’re pulling BigQuery exports, which most small operators aren’t.

    This isn’t a bug. It’s a design decision Google made to keep the product performant. But it catches people by surprise because GA4’s interface doesn’t warn you when you’re approaching the limit, and old Universal Analytics didn’t have this restriction in the same way.

    How the 25-parameter limit actually works

    When you send a custom event to GA4—say, newsletter_signup with parameters like source, landing_page, referrer, and email_domain—those parameters need to be manually registered as custom dimensions in the GA4 admin panel before they show up in reports.

    GA4 gives you:

    • 25 custom dimensions (event-scoped)
    • 25 custom dimensions (user-scoped)
    • 50 custom metrics (numeric values)

    Event-scoped dimensions are what most operators burn through first. These are things like button_label, video_title, product_category—anything that describes a single interaction.

    Once you hit 25 event-scoped dimensions, you’re done. You can’t add more without archiving an existing one. And archiving doesn’t free up the slot—it just stops collection. Historical data stays, but the dimension becomes read-only.

    What breaks when you hit the ceiling

    Let’s say you’re tracking newsletter signups across six different lead magnets. You’ve been sending lead_magnet_name as a parameter for months. Then you launch a new sponsored post tracking setup and add five more parameters: sponsor_name, placement_type, cta_variant, reader_segment, and content_topic.

    You go to GA4 Explore to build a report. The new parameters don’t show up in the dimension picker. You check the raw event in DebugView—it’s firing correctly. The data is being sent. But it’s not indexed, so it’s not queryable.

    Here’s what you lose:

    • You can’t segment audiences by that parameter
    • You can’t build Explore reports around it
    • You can’t use it in Looker Studio dashboards
    • You can’t create conversion funnels that filter by it

    The only workaround is BigQuery, which requires a GA4 360 subscription (starting at $50,000/year) or a manual export setup most indie operators won’t bother with.

    How to plan your parameter budget

    The fix isn’t technical—it’s editorial. You need to treat custom dimensions like a finite resource and plan what you track before you start sending events.

    Start by auditing what you’re already using. Go to Admin > Data display > Custom definitions in GA4. You’ll see a list of every registered dimension and metric. Count them. If you’re above 20, you’re in the danger zone.

    Then ask: Which of these dimensions do I actually query? Most operators register parameters “just in case” and never look at them again. Archive anything you haven’t used in a report in the last 90 days.

    For new tracking, consolidate where you can. Instead of separate parameters for lead_magnet_name, lead_magnet_category, and lead_magnet_format, use a single lead_magnet_id and map it to a lookup table in your reporting layer. Instead of tracking button_color, button_size, and button_position separately, combine them into one button_variant string like blue_large_sidebar.

    This isn’t elegant, but it works. And it keeps you under the limit.

    The non-obvious tip: namespace your parameters early

    If you’re starting fresh or still have slots available, prefix your custom parameters by category. Use form_name, form_step, form_source instead of generic names like name, step, source. It makes your dimension list easier to scan, reduces the chance of accidental overwrites, and helps you spot redundant tracking before you register a new dimension.

    And when you do hit the limit? Don’t panic and start archiving things randomly. Export your current Explore reports first, note which dimensions they depend on, and only archive parameters that aren’t load-bearing.

    Want more breakdowns like this? Reply with the analytics edge case that’s been tripping you up—we’ll cover it in a future issue.

  • Google Search Console click data lag: when yesterday’s spike appears next week

    Google Search Console click data lag: when yesterday’s spike appears next week

    Google Search Console click data lag: when yesterday's spike appears next week
    Photo by SumUp on Unsplash

    Google Search Console is the one traffic source most operators check daily. But the data you’re looking at isn’t live—and the lag isn’t consistent across all metrics.

    If you published something Monday and check GSC Tuesday morning hoping to see clicks, you’ll usually find nothing. The delay isn’t a bug. It’s how Google batches and processes impression and click data across billions of queries. Understanding the lag helps you avoid premature conclusions and know when a traffic change is real.

    What updates when

    Search Console breaks down into two reporting areas: Performance (clicks, impressions, CTR, position) and Index Coverage (crawl status, indexing errors).

    Performance data—the clicks and impressions you care about most—typically lags 24 to 48 hours. If you’re checking data for July 27, expect it to stabilize by July 29. The most recent day in the chart will show partial data and often updates throughout the day, but it won’t be complete until the following day.

    Index coverage updates faster. New pages submitted via sitemap or manually requested usually appear within hours. But even here, the last crawled timestamp can lag by a day.

    The Performance tab also separates Discover and Google News traffic into separate reports. These update on similar schedules, but Discover data in particular can take up to three days to fully populate—especially if your content spiked in a geography outside your primary market.

    When partial data misleads

    The most common mistake: seeing a spike in yesterday’s clicks, assuming it’s real, and writing about it—only to watch the number shrink the next day as the data finalizes.

    This happens because Google processes high-volume queries faster than long-tail ones. If your content ranks for a competitive term, early click data may show up within 24 hours. But if most of your traffic comes from hundreds of tiny queries, those clicks trickle in over 48 hours.

    The inverse is also true. A sudden drop in clicks for “yesterday” might just mean the data hasn’t loaded yet. Wait a full two days before diagnosing a traffic problem.

    One tell: if the impressions number is suspiciously low compared to the prior week’s average, the data isn’t done processing. Impressions and clicks populate together, but impressions are a higher-volume signal and tend to fill in first.

    What this means for your daily routine

    If you check GSC every morning, set your date filter to end two days ago—not yesterday. That gives you complete data and eliminates false positives.

    For real-time traffic signals, use Google Analytics 4 instead. GA4’s realtime report updates within minutes. It won’t tell you which queries drove the traffic, but it will confirm whether a spike is happening. Then return to GSC two days later to see the query breakdown.

    If you’re running a time-sensitive campaign—a product launch, a news piece, or a seasonal promo—don’t rely on GSC to measure day-one performance. Use GA4, your server logs, or a third-party rank tracker that pings hourly. GSC is for post-mortems, not live monitoring.

    When the lag stretches longer

    Occasionally, GSC data lags beyond 48 hours. This usually happens during Google algorithm updates, when the system is recalculating rankings and re-processing impression data across the index.

    If you notice a three- or four-day gap, check the Google Search Status Dashboard. Google rarely announces processing delays publicly, but operator communities on Reddit and Twitter will surface it quickly.

    Another edge case: if your site has fewer than a few hundred impressions per day, GSC sometimes batches your data less frequently. Small sites may see 72-hour lags as normal. The threshold isn’t documented, but anecdotally, sites under 500 daily impressions see slower updates.

    One workaround: the Search Console API sometimes surfaces data slightly faster than the web UI, especially for bulk queries. If you’re pulling GSC data into a dashboard or spreadsheet, the API’s freshness can beat the UI by a few hours—but you’re still looking at a 24-hour minimum lag.

    Want to catch these operator tips earlier? Subscribe to One Two Three Send—one focused article every morning, no fluff.

    The short version: treat Search Console as a lagging indicator. It’s authoritative, but it’s not fast. For daily decisions, pair it with GA4. For weekly analysis, let the data settle before you draw conclusions.

  • Analytics pixel load order changes what conversion data you see

    Analytics pixel load order changes what conversion data you see

    Analytics pixel load order changes what conversion data you see

    Most solo operators install analytics pixels the same way: copy the snippet, paste it into a header tag manager or WordPress plugin, and move on. But the order those scripts execute changes which events get tracked, how sessions are attributed, and whether conversions appear at all.

    Load order isn’t a hosting quirk—it’s a silent filter on your data. When a payment confirmation fires before your analytics library finishes initializing, that conversion never gets logged. When a UTM parameter gets stripped by a redirect before your tracking pixel reads it, attribution breaks. And because these failures happen in milliseconds, you won’t notice until you’re reconciling Stripe revenue against reported conversions and the numbers don’t match.

    What happens when pixels fire out of sequence

    Most analytics platforms—Google Analytics 4, Plausible, Fathom, Mixpanel—need their base library loaded before any event tracking calls execute. If a custom event fires while the library is still downloading, it gets dropped. No error. No retry. The event just doesn’t exist in your dashboard.

    This happens most often on single-page apps, checkout flows, and any page where a conversion event fires on page load rather than user action. A typical failure case: you use a Zapier webhook to log a new subscriber in your CRM, and that webhook triggers a thank-you page redirect. If the redirect happens before GA4’s gtag('event', 'conversion') call completes, the conversion never reaches Google’s servers.

    Even when the base library loads in time, the order pixels fire relative to each other determines what data each one sees. If a Facebook Pixel fires before a UTM-stripping redirect, it captures source attribution. If it fires after, it logs the visit as direct traffic. Neither setup is “wrong”—but one gives you attribution data you can act on, and the other doesn’t.

    Where load order gets decided

    If you’re using a tag manager—Google Tag Manager, Matomo Tag Manager, or a WordPress plugin like Site Kit—load order is controlled by trigger rules and priority settings. Most operators leave these at default, which means tags fire in the sequence they were added. That’s fine until you add a new pixel six months later and it fires last, missing fast-loading events.

    GTM lets you set a “tag firing priority” number. Higher numbers fire first. If your GA4 base tag has priority 10 and a custom conversion event has priority 5, the event will try to fire before the library exists. Set the base tag to a higher number than any dependent event, and the problem goes away.

    In WordPress, most analytics plugins inject scripts into wp_head or wp_footer hooks. The order depends on plugin load sequence, which is alphabetical by folder name unless you force a specific order with a plugin like Code Snippets or a custom functions.php entry. If two plugins both hook into wp_head with default priority (10), the one whose folder name comes first alphabetically loads first.

    That’s why renaming a plugin folder or switching from “Google Analytics for WordPress” to “Site Kit by Google” can silently change your tracking setup.

    What breaks when checkout flows redirect too fast

    Payment processors and course platforms almost always redirect immediately after a transaction. Stripe Checkout, Gumroad, and most hosted course tools send the buyer to a confirmation URL within milliseconds of payment. If your analytics pixel hasn’t finished sending the conversion event by the time that redirect fires, the event is abandoned mid-request.

    The fix is to delay the redirect until tracking calls return a success callback—but most platforms don’t expose that hook. Stripe Checkout’s success_url is a hard redirect; there’s no “wait for analytics” option. The workaround is to fire the conversion event on the confirmation page itself, not the checkout page. That way, even if the redirect is instant, the event fires after the user lands on a stable URL.

    This also means your conversion tracking needs to deduplicate. If someone refreshes the thank-you page, the event fires again. GA4 handles this with a client ID and session timestamp; other tools require a transaction ID parameter you pass through the URL and check against a cookie or localStorage flag.

    How to audit what’s actually firing

    Open your browser’s network tab, filter by “analytics,” “gtag,” “pixel,” or the domain of your tracking provider, and reload the page. Watch the order requests appear. If a conversion event request fires before the base library’s analytics.js or gtag.js, you’ve found the problem.

    For checkout flows, test with network throttling enabled (Chrome DevTools lets you simulate slow 3G). If your conversion event only fires on fast connections, it’s a timing race you’ll lose on mobile networks.

    If you’re using Postmark or another transactional email service to send purchase confirmations, compare the timestamp of the email sent against the timestamp of the conversion event in your analytics dashboard. If the email consistently arrives before the event logs, your tracking is firing too late—or not at all.

    Want more operator-to-operator breakdowns of what actually works? Subscribe to One Two Three Send—one article daily, no fluff, no affiliate spam unless the tool genuinely fits the topic.

  • Traffic attribution breaks when UTM parameters get stripped by platforms

    Traffic attribution breaks when UTM parameters get stripped by platforms

    Traffic attribution breaks when UTM parameters get stripped by platforms
    Photo by Tim Mossholder on Unsplash

    You tag every link with UTM parameters. You check Google Analytics. Half your traffic shows up as “direct” even though you know it came from email or social.

    The problem isn’t your tagging discipline—it’s that platforms strip UTM parameters before the user ever reaches your site. Email clients do it for privacy. Social apps do it to hide referral data. Link shorteners do it by accident when they redirect through multiple hops.

    If you’re running a content business and relying on UTM tags to measure what’s working, you need to know what actually survives the trip from send to click.

    What strips UTM parameters and when

    Email clients are the worst offenders. Apple Mail Privacy Protection, Gmail’s link scanner, and Outlook’s Safe Links feature all rewrite URLs before a human clicks them. Sometimes the UTM parameters survive the rewrite. Sometimes they don’t.

    Apple Mail strips them inconsistently—if the recipient opens the email on iOS with tracking protection enabled, parameters often vanish. Gmail’s link scanner preserves them most of the time, but if the email gets forwarded or opened in a third-party client, all bets are off.

    Social platforms strip them deliberately. LinkedIn removes UTM tags from outbound links in posts (not ads) to prevent attribution leakage to competitors. Twitter used to preserve them but now strips utm_source and utm_medium on mobile app clicks. Facebook Messenger rewrites URLs entirely and drops everything after the ? unless you’re using a Facebook pixel.

    Link shorteners compound the issue. Bit.ly and TinyURL preserve parameters if you paste the full tagged URL into their interface. But if you use their browser extensions or API without explicitly appending the parameters after shortening, the UTM tags get truncated. Redirect chains—where one short link points to another short link—drop parameters at each hop.

    What this looks like in your analytics

    You send a newsletter with utm_source=newsletter&utm_medium=email&utm_campaign=july tagged on every link. You check Google Analytics 4 the next day. Traffic shows up, but 40% is labeled “direct / none” instead of “newsletter / email.”

    The same thing happens with social posts. You share a link on LinkedIn with full UTM tagging. Analytics shows a spike in traffic, but the source reads “direct” or gets misattributed to Google if someone Googled your brand name after seeing the post.

    This isn’t a tracking bug. It’s parameter stripping in action. The user clicked your link, but the UTM tags didn’t survive the platform’s URL rewrite, privacy scanner, or redirect logic.

    How to track attribution when UTM tags fail

    First option: use campaign-specific landing pages instead of UTM parameters. If you’re promoting a guide, create /guide-linkedin and /guide-newsletter as unique URLs that 301 redirect to the real page. You lose some SEO link equity with redirects, but you gain reliable source tracking even when parameters get stripped.

    Second option: append a custom parameter that platforms ignore. Instead of utm_source, use ref=newsletter or via=linkedin. These aren’t standard tracking parameters, so email clients and social platforms don’t recognize them as privacy risks and leave them alone. You’ll need to configure your analytics tool to read these custom parameters as traffic sources—Google Analytics 4 lets you do this with custom dimensions, and most self-hosted tools like Plausible or Fathom support query parameter tracking out of the box.

    Third option: track at the application layer instead of the URL. If someone clicks through from your newsletter and immediately signs up or downloads something, log the referrer in your database at the moment of conversion. This doesn’t help with anonymous traffic, but it gives you attribution for actions that matter—subscribers, buyers, trial signups.

    Fourth option: accept that some traffic will always be misattributed and focus on channel-level trends instead of click-level precision. If you send a newsletter on Tuesday and see a 300% traffic spike on Tuesday afternoon, you don’t need perfect UTM tracking to know the newsletter worked. Same with social posts—watch for correlated traffic increases within an hour of posting.

    When UTM parameters still work

    Paid ads preserve UTM tags more reliably than organic links. Facebook Ads, Google Ads, and LinkedIn Ads all pass UTM parameters through their tracking pixels without stripping them, because the platforms want attribution data for their own dashboards.

    Direct website embeds work, too. If you link from your own blog post to another page on your site, UTM tags survive because there’s no intermediary platform rewriting the URL.

    SMS links preserve parameters as long as you’re not using a link shortener. Twilio, SimpleTexting, and most SMS platforms send the raw URL without modification.

    The key is knowing which channels strip parameters and planning around them instead of assuming your tagging strategy works everywhere.

    Have a question about tracking, attribution, or analytics for your online business? Reply to this email—we answer reader questions every Sunday.

  • Why solo operators need fewer analytics dashboards, not better ones

    Why solo operators need fewer analytics dashboards, not better ones

    Why solo operators need fewer analytics dashboards, not better ones
    Photo by path digital on Unsplash

    Open question: how many browser tabs do you have pinned right now for analytics tools?

    Google Analytics 4. Plausible or Fathom. Your newsletter platform’s dashboard. Stripe for revenue. Maybe a social media scheduler with engagement stats. Maybe a WordPress stats plugin.

    You’re swimming in data, but you still can’t answer basic questions without cross-referencing three platforms and doing the math in a spreadsheet.

    The problem isn’t that your analytics tools are bad. It’s that you’re using too many of them.

    The dashboard creep problem

    Every tool you add promises one thing it tracks better than the rest. Fathom gives you clean pageview data. GA4 tracks events and funnels. Your newsletter platform counts opens and clicks. Stripe tells you MRR.

    None of them talk to each other.

    So you end up with this: a reader clicks a link in your newsletter, lands on a blog post, scrolls to a paywall, and converts to a paid subscriber. That’s one journey. But you’re tracking it in four places:

    • Newsletter click-through in MailerLite or Beehiiv
    • Pageview and scroll depth in GA4 or Plausible
    • Conversion event in Stripe
    • Subscriber count in your membership plugin or Substack dashboard

    Want to know your true conversion rate from email to paid? You’re exporting CSVs and building pivot tables.

    Most solo operators don’t have time for that. So they stop looking. They check revenue once a week, glance at traffic when it spikes, and ignore everything else.

    Consolidation is a feature, not a compromise

    The knee-jerk response is to buy a better integration tool. Zapier, Make, or a dedicated analytics warehouse like Segment.

    That works if you’re running a team with a data analyst. For a solo operator, it’s just another dashboard to maintain.

    Here’s the alternative: pick one source of truth per question you actually need to answer, and delete the rest.

    If you run a paid newsletter, your newsletter platform’s dashboard is your primary metric. Revenue, open rates, churn—it’s all there. You don’t need GA4 tracking the same pageviews unless you’re also running a separate blog with ad revenue.

    If you run a content site with affiliate income, your traffic analytics tool (GA4, Plausible, Fathom) is the anchor. Stripe is secondary—you check it when payouts arrive, not daily.

    If you sell a course or productised service, Stripe or your payment processor is the single source of truth. Traffic is context, not the core metric.

    One dashboard per business model. Everything else is noise.

    What to delete right now

    Start with anything that duplicates a metric you already track elsewhere.

    If your newsletter platform shows subscriber count and growth rate, you don’t need a separate spreadsheet updating those numbers weekly. If Stripe shows MRR and churn, you don’t need a SaaS metrics dashboard calculating the same figures with a two-day delay.

    Delete any tool you haven’t logged into in the last 30 days. If you’re not checking it, you’re not using the data. And if you’re not using the data, the tool is just spending CPU cycles and cluttering your mental model.

    Delete any analytics layer that requires manual export to be useful. If you have to download a CSV, open Excel, and join two tables to answer a question, that tool isn’t serving you—it’s creating work.

    The one dashboard you actually need

    Here’s what works for most solo operators:

    One real-time traffic dashboard (Plausible, Fathom, or GA4 if you’re already fluent in it). One revenue dashboard (Stripe, or your newsletter platform if you’re subscription-first). One weekly export of the metric that matters most to your business model—usually traffic sources, conversion rate, or churn.

    That’s it. Three views, not six. You can check all three in under five minutes, and you’ll have enough context to make decisions without second-guessing the data.

    If you can’t make a decision with the data a tool provides, the tool isn’t solving a problem—it’s becoming one.

    Want more operator-to-operator breakdowns like this? Subscribe to One Two Three Send and get one article like this in your inbox every week—no fluff, no filler, just the infrastructure decisions that matter.

    Heads up — some links in this article are affiliate links. If you sign up through them, we may earn a small commission at no extra cost to you. We only recommend tools we use ourselves.

  • Google Analytics 4 event parameters: which ones actually fire

    Google Analytics 4 event parameters: which ones actually fire

    Google Analytics 4 event parameters: which ones actually fire
    Photo: Ajiro Shinpei via Wikimedia Commons (CC BY-SA 4.0)

    Google Analytics 4 lets you attach up to 25 custom parameters to each event. That sounds generous until you realize that half of them might never show up in your reports—and the platform won’t tell you which ones got dropped until you go looking.

    If you’re running a content business and relying on GA4 to track subscriber behavior, link clicks, or content engagement, this matters more than you think. Parameters that don’t fire mean attribution you can’t trace and conversion paths you can’t rebuild.

    Why parameters get dropped

    GA4 has three silent filters that strip parameters before they hit your data stream:

    Character limits. Parameter names max out at 40 characters. Values cap at 100 characters. Cross either threshold and the entire parameter vanishes. No error message, no warning in the real-time view—it just doesn’t arrive.

    Reserved parameter names. GA4 reserves roughly 50 parameter names for its own use. If you accidentally name a custom parameter something like page_location, session_id, or user_id, your value gets overwritten by GA4’s automatic collection. The list isn’t published in one place; you have to cross-reference the automatically collected events documentation to catch conflicts.

    Data type mismatches. GA4 infers parameter types from the first value it sees. If your first event sends article_id as a string and the second sends it as an integer, one of them won’t register as a custom dimension. The platform doesn’t reconcile types retroactively, so you’re left with partial data and no alert.

    How to audit what’s actually firing

    Most operators check the real-time report and assume everything’s working. That view only shows the last 30 minutes and doesn’t surface parameter-level failures.

    The better approach: open the DebugView panel in GA4. It requires enabling debug mode in your Google Tag Manager container or adding ?debug_mode=true to your site URL if you’re using gtag.js directly. DebugView shows every event and parameter as it arrives, including which ones GA4 accepted and which ones it ignored.

    Run a test session where you trigger each custom event you care about—newsletter signups, affiliate link clicks, content unlocks, purchase completions. Watch the parameter list for each event. If a parameter you sent isn’t listed, it got dropped.

    Once you know which parameters aren’t firing, check their names against the reserved list, confirm their character counts, and verify that your tag configuration isn’t sending mixed data types.

    When to use event-scoped vs. user-scoped parameters

    GA4 parameters come in two flavors: event-scoped and user-scoped. Event-scoped parameters attach to individual actions—link_url, video_title, form_id. User-scoped parameters attach to the visitor across sessions—subscription_tier, signup_source, customer_ltv.

    The distinction matters because GA4 limits you to 50 custom event-scoped dimensions and 25 custom user-scoped dimensions per property. If you’re tracking a membership site or a newsletter with multiple tiers, user-scoped parameters let you segment reports without burning through event slots.

    But user-scoped parameters don’t populate immediately. GA4 only attaches them after the first event in a session fires. If you need to filter reports by subscription_tier and a visitor’s first event is a page view, the tier parameter won’t be available until the second event. That creates reporting gaps if you’re trying to attribute conversions by tier.

    The workaround: send a silent user_properties_set event on page load with all your user-scoped parameters, then fire your standard events afterward. It’s an extra event in your tag setup, but it ensures parameters are available from the first interaction.

    One non-obvious fix

    If you’re sending parameters through Google Tag Manager and they’re still not showing up in GA4, check your tag’s Fields to Set configuration. GA4 tags in GTM have two places to define parameters: the “Event Parameters” section and the “Fields to Set” section. Event parameters go to GA4. Fields to Set go to the Google tag itself and don’t always pass through to GA4 reports.

    Most operators fill out Fields to Set by mistake and wonder why their custom dimensions stay empty. Move everything to Event Parameters unless you’re explicitly configuring a Google Ads conversion tag.

    If you’re running attribution reporting across multiple tools—GA4, your newsletter platform, and a course LMS—make sure your parameter names stay consistent. GA4 doesn’t auto-map source to utm_source or email to user_email. Pick a naming convention and enforce it across every tag, webhook, and API call.

    Want more breakdowns like this? Reply with the analytics setup you’re trying to debug—I’ll cover it in a future issue.