Article

How a Privacy Extension Silently Broke My Booking Funnel

  • observability
  • analytics
  • conversion
  • startup architecture
  • silent failures

How a Privacy Extension Silently Broke My Booking Funnel

I was testing my landing page and clicked the booking CTA.

Nothing happened.

No error. No crash. No console warning.

Just nothing.

For a site whose job is to turn technical trust into booked audits, that is the worst kind of failure.

This was not a dramatic outage.

It was worse than that.

It was a silent failure on the revenue path.

The immediate cause was a privacy extension.

The deeper cause was architectural: analytics and external navigation were sitting inside the critical path of a conversion event.

That is exactly the class of problem I write about in What Actually Breaks When Your SaaS Gets Its First 1,000 Users and 12 Commits to a Reliable Backend. Systems usually fail quietly before they fail loudly.

This time, the system was my own site.


The Failure Path

That was the whole bug.

Not a stack trace.

Not a backend crash.

Just a click that disappeared.

I did not need large traffic to find it.

I just needed one blocked browser on a high-intent path.


What The Debugging Actually Looked Like

The first few minutes were misleading because the failure did not look like a normal application bug.

SignalEasy InterpretationWhat Was Actually Happening
Booking CTA did nothingBroken button or bad handlerA third-party dependency in the click path was being blocked
Console stayed cleanNo real problemThe browser extension was intercepting requests before app code threw
Analytics looked incompleteInstrumentation driftRequests to us.i.posthog.com were being blocked
Scheduler did not openCalendly issueThe external handoff itself was also fragile under blocker/popup protection

The Network tab told the truth.

Requests to us.i.posthog.com were getting blocked.

Disabling the extension changed behavior immediately.

That got me to the first fix fast.

But it also exposed the deeper problem.

Even after analytics started flowing again, the booking flow itself was still brittle.

The first click was trying to do too much.

Track the action.

Open an external scheduler.

Rely on a third-party domain.

Hope the browser and extension approved the entire chain.

That is not a reliable conversion path.

The bug was not "PostHog failed."

The bug was "a third-party dependency was inside the critical path of user intent."


Why This Class Of Failure Is So Easy To Miss

Privacy extensions do not need to break your JavaScript to break your funnel.

Most of the time they only need to do one of two things:

  • block requests to known analytics domains
  • suppress navigation patterns that look like tracking or popup behavior

That is enough.

If your click path depends on either of those things succeeding, the user can lose the action without ever seeing an error.

This is why silent failures are more expensive than crashes.

Crashes get reported.

Silent failures just lower conversion.

If you want a quick diagnostic for the second class of problem, this is the shape to watch for:

const opened = window.open(url, "_blank", "noopener,noreferrer")

if (!opened) {
  // The browser or an extension blocked the handoff.
  // You need a visible fallback instead of pretending it worked.
}

That check is useful.

It is not the whole fix.

The better question is: why is the first click doing a popup-style external handoff at all?

For technical audiences, this is not edge-case behavior.

PostHog recommends a first-party proxy specifically because ad blockers commonly intercept analytics requests, and their proxy docs frame it as a reliability improvement, not just a measurement tweak. They cite typical recovery in the 10-30% range depending on audience and blocker usage. For a site aimed at founders and engineers, that is material enough to distort funnel judgment.


Fix 1: Move Analytics Behind A First-Party Path

The first concrete fix was routing browser analytics through my own domain instead of sending the client directly to PostHog.

In the app, that is just a rewrite:

async rewrites() {
  return {
    beforeFiles: [
      {
        source: "/ingest/:path*",
        destination: "https://us.i.posthog.com/:path*",
      },
    ],
  }
}

And the client points api_host at the same-origin /ingest path instead of the raw PostHog domain.

This is the right first move.

It made analytics collection materially more reliable.

If you use PostHog, their reverse proxy docs cover the setup patterns directly, so I will not turn this post into yet another step-by-step tutorial.

But the proxy only solved part of the incident.

It fixed analytics.

It did not fully fix the booking funnel.

Because the scheduler handoff was still an external dependency.

And high-intent external controls were still too close to analytics autocapture.

That led to a second, smaller but important change: opt critical external controls out of autocapture with ph-no-capture and send explicit events instead.

Analytics should observe the handoff.

It should not sit inside it.


Fix 2: Stop Making The First Click Leave The Site

The more important fix was not technical.

It was a UX change.

The first click no longer tries to jump directly to the scheduler.

It opens a first-party handoff modal.

That is now the booking boundary.

The click records intent and keeps the user in a surface I control.

Only after that does the user choose how to continue.

The initial click path is intentionally simple:

trackCtaClick({
  cta_name: ctaName,
  cta_location: ctaLocation,
  destination: bookingUrl,
})

trackBookingIntent({
  source_surface: bookingSourceSurface ?? ctaLocation,
  destination: bookingUrl,
  cta_name: ctaName,
  cta_location: ctaLocation,
})

setIsOpen(true)

That is a much safer contract.

The first click stays local.

The user gets feedback immediately.

Intent is recorded before any external dependency is involved.

Inside the modal, the options are explicit:

  • open the scheduler in a new tab
  • copy the booking link
  • email me directly

The implementation is simple on purpose:

<a
  href={bookingUrl}
  target="_blank"
  rel="noopener noreferrer"
  className="ph-no-capture"
>
  Open in new tab
</a>

<button type="button" onClick={handleCopyLink}>
  Copy booking link
</button>

<a href={mailtoHref} className="ph-no-capture">
  Email hello@eliasfeijo.dev
</a>

No fancy transition logic.

No silent redirect.

No pretending the scheduler definitely opened.

Just a clear handoff and visible fallback paths.

The modal is not decoration.

It is a reliability boundary.

This is the same pattern you see in better payment redirects and OAuth handoffs.

The user should know they are leaving your surface.

And if the third-party step fails, they should still have a path forward.


Fix 3: Turn The Handoff Into An Observable Funnel

The original failure hurt because it was silent.

There was no good intermediate signal between "CTA was shown" and "Calendly probably opened."

Now there is a measurable chain:

  • cta_clicked
  • booking_intent
  • booking_handoff_action

And the downstream handoff action is explicit:

  • open_in_new_tab
  • open_direct_link
  • copy_booking_link
  • email_support

That is what the handoff event looks like in code:

trackBookingHandoffAction({
  action: "open_in_new_tab",
  handoff_variant: "modal",
  booking_destination: bookingUrl,
  cta_name: ctaName,
  cta_location: ctaLocation,
  source_surface: bookingSourceSurface ?? ctaLocation,
})

That changes the problem from invisible to diagnosable.

If booking_intent is healthy but downstream handoff actions are weak, I know the issue is in the handoff layer.

If copy-link or email fallback usage spikes, I know the scheduler path is degrading.

If analytics goes dark again, funnel proportions change in a way I can actually inspect.

This is observability applied to conversion.

Same principle.

Different surface.


The Broader Lesson

The real problem was not PostHog.

The real problem was not Calendly.

The real problem was treating third-party integrations as if they were reliable local infrastructure.

That assumption fails in three ways.

First: analytics is not optional infrastructure if your growth decisions depend on it.

If blocked analytics changes attribution, funnel interpretation, or CTA analysis, then it belongs in your reliability model.

Second: any external browser dependency on a high-intent path is a potential single point of failure.

Payments.

Schedulers.

Chat widgets.

Identity providers.

They can all fail partially.

And partially is the dangerous mode.

Third: silent failures are the most expensive kind.

Crashes create noise.

Silent failures create doubt.

Was demand lower this week?

Did CTA copy get worse?

Was traffic colder?

Or were users clicking and getting nothing?

That uncertainty is expensive.

It delays the fix.

It pollutes product judgment.

It erodes trust in the numbers.

This is why the correct solution was architectural, not just technical.

The reverse proxy was a configuration change.

The handoff modal was a design change.

Both were necessary.

One of the questions in my Backend Risk Self-Assessment asks whether your alerts would catch a payment provider silently failing.

This incident was the analytics-and-conversion version of the same question.

Would I know if a high-intent user tried to book and nothing happened?

Before this fix, the honest answer was no.


Final Thought

If your landing page or product relies on third-party services, test the failure path directly.

Turn on uBlock.

Use Brave.

Click every CTA that matters.

Open DevTools.

Watch the Network tab.

See what happens when analytics, chat, payments, or scheduling do not get a clean path to their vendor domains.

Reliable systems are not the ones with the most integrations.

They are the ones that still behave predictably when integrations are partially unavailable.

That is the live incident version of the same argument behind What Actually Breaks When Your SaaS Gets Its First 1,000 Users and 12 Commits to a Reliable Backend.

If you want a structured way to inspect those failure modes in your own stack, start with the Backend Risk Self-Assessment.

If you already know you need a deeper pass, the architecture audit is the service built for exactly this kind of problem.