Skip to content

feature: add PostHog reverse proxy to bypass ad blockers#145

Merged
KIvanow merged 7 commits intomasterfrom
improvement/posthog-proxy
May 7, 2026
Merged

feature: add PostHog reverse proxy to bypass ad blockers#145
KIvanow merged 7 commits intomasterfrom
improvement/posthog-proxy

Conversation

@KIvanow
Copy link
Copy Markdown
Member

@KIvanow KIvanow commented May 4, 2026

Adds a /ingest proxy route so PostHog events are sent through the app's own domain instead of directly to us.i.posthog.com, preventing ad blocker interference.

  • Vite dev server proxies /ingest → https://eu.i.posthog.com
  • NestJS controller handles /ingest/* in production via native fetch
  • Unit and e2e tests cover URL routing, body/header forwarding, and error codes

Summary

Changes

Checklist

  • Unit / integration tests added
  • Docs added / updated
  • Roborev review passed — run roborev review --branch or /roborev-review-branch in Claude Code (internal)
  • Competitive analysis done / discussed (internal)
  • Blog post about it discussed (internal)

Note

Medium Risk
Adds a new unauthenticated /ingest/* proxy path and adjusts global API prefixing to exclude it, which can affect routing and introduces an outbound request surface area. Risk is moderate but scoped because the upstream host is fixed via POSTHOG_HOST and behavior is covered by unit/e2e tests.

Overview
Adds a new PosthogProxyModule exposing /ingest/* that forwards requests to POSTHOG_HOST via fetch, passing through method, JSON body (for non-GET/HEAD), and content-type, and returning upstream status/body/content-type.

Updates production routing to keep /ingest/* outside the /api global prefix, and configures Vite dev server to proxy /ingest to PostHog. Adds unit tests for URL rewriting/headers/body handling and an e2e suite validating happy-path and upstream error status forwarding.

Reviewed by Cursor Bugbot for commit a30163f. Bugbot is set up for automated code reviews on this repo. Configure here.

KIvanow added 2 commits May 4, 2026 10:18
Adds a /ingest proxy route so PostHog events are sent through the app's
own domain instead of directly to us.i.posthog.com, preventing ad blocker
interference.

- Vite dev server proxies /ingest → https://us.i.posthog.com
- NestJS controller handles /ingest/* in production via native fetch
- Unit and e2e tests cover URL routing, body/header forwarding, and error codes
@KIvanow KIvanow requested a review from jamby77 May 4, 2026 07:21
Comment thread apps/web/vite.config.ts
Comment thread apps/api/src/posthog-proxy/posthog-proxy.controller.ts Outdated
Comment thread apps/web/vite.config.ts
envDir: path.resolve(__dirname, '../..'),
server: {
port: 5173,
proxy: {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have the controller, why don't we proxy to it directly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's one fewer network hop (browser → Vite → PostHog vs browser → Vite → NestJS → PostHog)

Comment thread apps/api/src/posthog-proxy/posthog-proxy.controller.ts
…able in production

Without this, setGlobalPrefix('api') mounts the PosthogProxyController at
/api/ingest/* while the PostHog SDK and Vite proxy both send to /ingest/*,
causing POST requests to 404 in production.
Comment thread apps/api/src/main.ts Outdated
Replace regex-style `ingest/(.*)` with named wildcard `ingest/*splat`
to be compatible with path-to-regexp v10+ used in NestJS v11.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a30163f. Configure here.

headers: {
'content-type': (req.headers['content-type'] as string) ?? 'application/json',
},
body: hasBody ? JSON.stringify(req.body) : undefined,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Body always JSON-stringified regardless of content type

Medium Severity

The proxy always re-encodes the body with JSON.stringify(req.body) but faithfully forwards the original content-type header. When a request arrives with a non-JSON content type (e.g., text/plain, which PostHog SDK uses for sendBeacon payloads), Fastify parses req.body as a raw string. Calling JSON.stringify on a string wraps it in quotes and escapes it, producing a double-encoded payload while the forwarded content-type still says text/plain. PostHog would fail to parse this mismatched request. Forwarding the raw body instead of always re-serializing as JSON would handle all content types correctly.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a30163f. Configure here.

@KIvanow KIvanow merged commit 2528b8f into master May 7, 2026
3 checks passed
@KIvanow KIvanow deleted the improvement/posthog-proxy branch May 7, 2026 06:52
@github-actions github-actions Bot locked and limited conversation to collaborators May 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants