// Growth

Building an ad network inside Webflow

Last year I started looking at an opportunity on the Cyberhaven marketing site. We had organic traffic landing on specific content under /blog and /infosec-essentials, and a separate library of downloadable assets sitting on other pages with no real path from one to the other. The question was simple: how do we give more visibility to the assets we have, and how do we turn some of that passing traffic into form fills?

The starting point

The constraint was Webflow. No ad server. No plugin market. No team of engineers to borrow. What I did have was the footer custom-code slot, PostHog for analytics, and a seed of JavaScript that a former colleague, Pete at Sigma Computing, had shared with me from an earlier project of his. I took Pete's starting point and built the Cyberhaven version on top of it.

How it works

The network runs entirely from a single footer script. It lives on Webflow, it uses PostHog for tracking, and it has no external dependencies. No npm packages, no build step, no CDN.

The script is wrapped in a self-executing function with two execution zones. A small site-wide block (around 3KB) runs on every page and handles conversion attribution. A larger collection-only block (around 32KB) only runs on the specific paths that serve ads. On every other page, the collection block is skipped entirely, which is about a 91% reduction in script execution across the rest of the site. Injecting custom code into someone else's CMS is always a performance risk, and keeping the non-collection cost near zero was a deliberate design call, not a lucky win.

There are three ad formats. An inline ad before the third heading of an article, with an image-proximity check so it doesn't land right next to an existing figure. A square sidebar ad attached to the table of contents. And a kicker ad at the end of the article. Mobile gets capped at two ads per page.

The tracking funnel

PostHog gets four events per ad: an impression when the ad renders, a viewable when the ad has been at least 50% visible for one full second, a click when the reader clicks, and a conversion when a form succeeds.

internal_ad_impression
internal_ad_viewable
internal_ad_click
internal_ad_conversion

Conversion is the interesting one, because it doesn't fire from the ad itself. It fires from the site-wide script, on a different page entirely, when a form submits successfully somewhere else on the site. The mechanism is simple. When a Webflow form succeeds, the page swaps the form element with a small thank-you message. I watch for that swap, read the last ad click from the browser's memory, check whether it's still within the 7-day attribution window, and fire the conversion event tied back to the right campaign.

The numbers

Full funnel tracking came online in August 2025. The window below covers 235 days since then, anonymized.

A scope note first. The network only serves ads on specific pages under /blog and /infosec-essentials. The traffic through those pages is almost entirely organic, and the pages themselves are top-of-funnel or mid-funnel content. That's a limited universe to start with, so the form fills attributed through this funnel are a stronger signal than their absolute count would suggest.

01
Impression
100%
02
Viewable
~55%
03
Click
~0.7%
04
Conversion
~0.3%
Four-stage funnel over 235 days, anonymized.

Roughly 55% of impressions turned into viewable impressions, meaning the ad was on screen long enough to count. Under 1% of impressions led to a click. About 0.3% of impressions ended with a filled form on the asset the reader was sent to.

The impression-to-viewable drop-off is normal for in-article ads. The real finding sits in the second half of the funnel. Once someone is curious enough to actually click on an internal ad, they convert to a form fill roughly 40% of the time. The ad is pointing them at an asset that's related to what they were already reading. They're not being sold to. They're being offered a next step.

Iteration

v1.0 shipped in January 2025. v2.0 landed thirteen months later, in February 2026.

In between there were small releases driven by things I noticed in the funnel. v2.0 was the bigger refactor: it pulled everything into the two-zone script, shipped the Campaign Manager as a self-contained HTML tool, and moved the site to a new form-success detection that finally closed the attribution loop I had been blind to up until then.

VersionShippedWhat changed
v1.0.0Jan 2025Initial release
v1.5.xMid 2025Viewability tracking + conversion attribution
v1.6.12025Weight-based ad rotation for A/B tests
v1.7.02025Excluded-author rule for inline ads
v1.7.1Oct 2025DLP Disconnect campaign launched
v2.0.0Feb 2026Two-zone refactor + Campaign Manager UI
v2.0.1Feb 2026Form-success detection closing the attribution loop

The excluded-author rule in v1.7 exists for a specific reason: certain pieces (a thoughtful CEO post, for example) shouldn't get an ad image dropped in the middle of them. It's more a vanity rule than a technical one, but it matters for the feel of the article.

Ideas I haven't shipped

A couple of improvements I've been thinking about but probably won't get to soon because of other priorities:

  • A stakeholder-facing UI so marketing can add, remove, and schedule campaigns themselves without touching the code or the single-HTML Campaign Manager. Something small, with role-based access and a changelog.
  • An image-gen layer on top of the campaign config, so someone adding a campaign can drop in a source image and get the three required ad dimensions generated automatically. AVIF optimization is already scripted. The gap is the creative generation.

Neither is a blocker for the network working. They're the kind of thing that makes the next person not have to wait on me, which is usually the real cost of a tool that works but requires a human in the loop.

What I'd keep

If I were rebuilding this from scratch, the architecture would stay exactly where it is. Two-zone script. Collection-gated heavy logic. Attribution following the visitor across the whole site. Everything running inside Webflow's footer slot with no external dependencies. It has held up through every content re-org Cyberhaven has thrown at it over the last year, and it still feels like the right shape for a marketing site that happens to care about funnel attribution.