Skip to content

Freemium watermarking

The watermark is the business model. Free traffic is never cut off; instead, high-volume free sites are served a watermarked poster frame — a visible prompt to subscribe — while everyone else gets a clean image. This page is the decision logic; Paid access is how a site earns its way back to clean.

For every thumbnail request the Worker resolves a single variant: clean or wm. The decision runs in this order:

  1. Paid bypass? If the request matches the entitlement allow-list (domain, IP, API key, or user-agent), variant = clean. Paying customers stop here and never see a watermark. See Paid access.
  2. Non-domain watermark cohort? If the request matches a reviewed non-domain signal (a branded X-Requested-With app identifier, a specific IP, or a branded user-agent that has been graduated into the cohort), variant = wm at 100%.
  3. No Referer header? variant = clean, by design. No-referrer traffic is dominated by datacenter renderers and SDK defaults; real browsers are a minority, so marking it would punish exactly the direct users the developer-friendly policy protects. (Historically this defaulted to a blanket watermark; it was narrowed to targeted cohorts.)
  4. Dev/local referrer? variant = clean. Development traffic is detected by the referrer hostlocalhost, 127.0.0.1, [::1], *.local, *.test, RFC-1918 addresses, and tunnel hosts — never by user-agent. The explicit early-return branch in the edge snippet (isDevCleanReferrer) is Planned; until it lands, dev hosts get the same clean result by being absent from the cohort.
  5. Referrer in the target cohort? If the Referer host is in the reviewed high-volume referrer cohort, apply the rollout percentage. The percentage is driven by a stable hash of the video ID so a given video is consistently marked or not. The rollout is currently pinned to 100% (hardcoded out of the KV hot path for cost), so a cohort referrer reliably gets wm.
  6. Otherwise variant = clean.

The threshold that decides which referrers belong in the cohort is a policy number, not a live per-request counter: 50,000 requests/month is the free clean allowance, with a 20% grace buffer (so outreach starts at 60,000/ month). Hosts crossing it are reviewed and added to the watermark cohort and the outreach list.

Inputs: Referer host, CF-Connecting-IP, User-Agent, X-Requested-With, the video ID (for the stable rollout hash), the entitlement allow-list, and the FORCE_ROLLOUT override env var.

Outputs: a variant of clean or wm, consumed by Caching & delivery to select or render the right image. In debug mode the decision is exposed via X-Thumbnail-Rollout, X-Thumbnail-Rollout-Reason, and the bypass/variant headers.

  • Ambiguous entitlement — when the allow-list signal can’t be read, the safe default is clean; a paying customer must never be watermarked.
  • Watermark render failure — fails open to the clean image (see Caching & delivery).
  • Reverse leak — a referrer-blind long-TTL edge cache can pin wm bytes on the bare URL, so no-referrer / direct traffic receives wm where the policy says clean. The policy is explicit: ambiguous traffic stays clean. Under the snippet delivery model the bare URL always stays clean (the watermark is a 307 redirect to a distinct wm.jpg key), and any pinned-wm bare URLs that violate the policy are purged as part of every host flip — coupled in the same release with worker-cohort removal so the miss path can’t re-pin wm.
  • Funnel gap (known issue) — a watermarked image is only a conversion lever if it points the viewer to /pricing. Wiring the watermark to the pricing funnel is the binding constraint on converting watermarked hosts.
  • Very high-volume referrers — reviewed individually as a separate expansion rather than flipped in bulk, because delivery surface and audience vary widely at that scale.
type WatermarkDecision = {
variant: "clean" | "wm"
reason:
| "paid-bypass"
| "non-domain-cohort"
| "no-referrer-default-clean"
| "dev-clean-referrer" // Planned: explicit isDevCleanReferrer branch
| "target-referrer-rollout"
| "default-clean"
rollout: number // 0.0–1.0; currently pinned 1.0 for the cohort
cohortMatch: boolean
}
// Policy thresholds (shared from @repo/core/vumbnail-pricing.ts)
const FREE_TIER_MONTHLY_REQUESTS = 50_000
const OUTREACH_GRACE_PERCENT = 20
const OUTREACH_MONTHLY_THRESHOLD = 60_000 // ceil(50_000 * 1.20)
  • No-referrer policy sign-off. No-referrer → clean is decided (see Decisions), but the written policy sign-off document is still required before the first purge wave runs.
  • Cohort graduation rules. When should a non-domain signal auto-graduate into the watermark cohort (volume, persistence, confidence), and which signals are always excluded (browsers, generic runtimes, crawlers, shared social/ search referrers)?

These are tracked on the global Open questions page.

  • 2026-06-14 — No-referrer requests are served clean, explicitly. Evidence: no-referrer traffic is dominated by datacenter renderers plus SDK defaults, with real browsers a minority. Watermarking it would hit the direct users the developer-friendly policy exists to protect. A written policy sign-off document is still required before the first purge (open).
  • 2026-06-14 — Dev/local traffic is served clean, detected by referrer host. localhost, 127.0.0.1, [::1], *.local, *.test, RFC-1918 ranges, and tunnel hosts match by referrer host — never by user-agent. The explicit snippet early-return (isDevCleanReferrer) is Planned, not yet built.
  • 2026-06-14 — The edge snippet is the deterministic watermark control point. A watermark is a 307 redirect to a distinct wm.jpg cache key; the bare URL stays clean. Flipping a host = adding it to WM_DOMAINS plus its current ids to HOT.
  • 2026-06-14 — Ambiguous traffic stays clean (reverse-leak policy). A live re-probe found delivery had flipped wm-dominant at the probed colo, with no-referrer users receiving wm from the referrer-blind long-TTL cache — a policy violation. Pinned-wm bare URLs that violate the policy are purged as part of any host flip, in the same release as worker-cohort removal so the worker’s miss path cannot re-pin wm.