CDPGuidesSupport

Headless Personalization

Code-driven personalization with Ours Privacy. Use window.ours_experiments.getVisitorContext() to access geo, UTM, device, and visitor status, and render targeted content in your own application code.

Headless personalization is the code-driven personalization pattern for Ours Privacy. Your application reads visitor signals (geo, UTM, device, visitor status, and the properties your personalization rules have accumulated) from window.ours_experiments.getVisitorContext() and renders targeted content directly in your own JavaScript.

Use this when you want to make rendering decisions from visitor signals. For the always-on rules concept, see Content Personalization. For experiment assignment APIs (which variant the visitor was assigned to), see the JavaScript SDK.


Quick Start

const ctx = window.ours_experiments.getVisitorContext();

// Geo-targeted content
if (ctx.geo?.country_region === 'TX') {
  showTexasComplianceBanner();
}

// Campaign-specific messaging
if (ctx.utm.source === 'meta' && ctx.visitor_status === 'new') {
  showFacebookWelcomeOffer();
}

// Device-specific layout
if (ctx.device.type === 'mobile') {
  showMobileOptimizedHero();
}

Browser Signals

Signals available immediately on page load.

UTM Parameters

ctx.utm;
// {
//   source: 'meta',
//   medium: 'paid',
//   campaign: 'spring-2026',
//   content: 'hero-cta',
//   term: 'privacy software'
// }

All values are string | null. Parsed from window.location.search on each page load.

Initial UTM Parameters

ctx.initial_utm;
// {
//   source: 'meta',
//   medium: 'paid',
//   campaign: 'spring-2026',
//   content: null,
//   term: null
// }

The visitor's UTM values from their first tracked arrival for the current experiment cookie, persisted in the experiment cookie. Set once on the first visit that carries any UTM parameter for that tracked visitor, then reused on later pageviews for the same visitor ID.

  • Returns null if the visitor has never arrived via a URL with UTM parameters.
  • Same shape as ctx.utm when present.
  • Useful for targeting or analytics based on the visitor's original acquisition channel, regardless of their current page.
// Show content based on original acquisition channel
if (ctx.initial_utm?.source === 'meta' && ctx.utm.source === null) {
  showReturningFacebookVisitorOffer();
}

Query Parameters

ctx.query_params;
// { utm_source: 'meta', ref: 'partner-123', promo: 'SAVE20' }

All current URL query parameters as a flat key-value object.

Visitor Status

ctx.visitor_status; // 'new' | 'returning'

Based on whether the current visitor ID has already been seen by the experiment runtime for this browser's experiment cookie.

Referrer

ctx.referrer; // 'https://google.com/search?q=...' | null

Device & Browser

ctx.device;
// {
//   type: 'mobile',        // 'desktop' | 'mobile' | 'tablet'
//   os: 'iOS',
//   browser: 'Safari',
//   screen_width: 390,
//   screen_height: 844,
//   language: 'en-US'
// }

Time

ctx.time;
// {
//   day_of_week: 2,        // 0 (Sunday) – 6 (Saturday)
//   hour: 14,             // 0-23, visitor's local time
//   timezone: 'America/Chicago'
// }

Geo and Device Signals

Use location and device signals to tailor your content.

Geolocation

ctx.geo;
// {
//   country: 'US',
//   country_region: 'TX',
//   city: 'Austin',
//   zip: '78701',
//   dma: '635',
//   dma_name: 'AUSTIN',
//   time_zone: 'America/Chicago'
// }

zip is the postal code. For U.S. visitors, dma is the designated market area code and dma_name is its name. All three are strings and may be unavailable independently. Check for missing values, including a null geo object, before personalizing.

if (ctx.geo?.zip === '10001' && ctx.geo.dma === '501') {
  showNewYorkOffer();
}

Device Type

ctx.geo?.is_mobile_viewer; // 'true' | 'false'
ctx.geo?.is_desktop_viewer; // 'true' | 'false'
ctx.geo?.is_tablet_viewer; // 'true' | 'false'

These optional flags complement the ctx.device object.


Accumulated Personalization Properties

ctx.properties;
// {
//   visited_pricing: true,
//   last_campaign: 'spring-2026',
//   pageview_count: 7
// }

The visitor traits your personalization property rules have accumulated, keyed by property key. Every value is a single string, number, or boolean, or null when the captured field was itself empty. Property rules never store nested objects or raw event payloads, so there is nothing to walk into.

Guard every read. A visitor who has not matched any property rule yet has an empty properties object, and properties itself is absent until the runtime has resolved the visitor:

// Boolean trait: compare explicitly instead of relying on truthiness, so an
// absent bag renders the default experience rather than throwing.
if (ctx?.properties?.visited_pricing === true) {
  showPricingFollowUp();
}

// Scalar trait
if (ctx?.properties?.last_campaign === 'spring-2026') {
  showSpringCampaignHero();
}

A value accumulated during the current page view reaches the browser on the visitor's next full page load. If your rendering decision has to react within the same page view, read the event you are already sending instead of waiting on the property.

Data Safety

Accumulated property values are delivered to the browser and can be queried with a visitor ID. Use only values that are safe for browser delivery. Do not use secrets, credentials, protected health information, or other confidential data in personalization rules.


Targeting Rules on Visitor Signals

Everything above is also available to the visitor targeting on a personalization rule or experiment, so you can gate a rule on a signal without writing any code. Name the signal as the rule key:

Rule keyReads
geo.country, geo.region, geo.city, geo.zip, geo.dma, geo.dma_nameVisitor location; DMA is available only for U.S. visitors
utm.source, utm.medium, utm.campaign, utm.content, utm.termUTM parameters on the current URL
initial_utm.source, initial_utm.medium, initial_utm.campaign, initial_utm.content, initial_utm.termThe visitor's original acquisition UTMs
device.type, device.os, device.browser, device.language, device.screen_width, device.screen_heightClient-side device and browser
time.hour, time.day_of_week, time.timezoneThe visitor's local time
query_params.<name>A single query parameter on the current URL
visitor_statusnew or returning
referrerThe document referrer
Any other bare keyAn accumulated personalization property with that key

A string rule value matches case-insensitively. An array of values matches if the signal equals any of them. Multiple keys on one rule must all match.

Accumulated property keys are matched as whole keys, so a rule key of visited_pricing reads the property named visited_pricing.


Event: visitor:ready

Subscribe to the visitor context becoming available after window.ours_experiments.init(...) runs:

window.ours_experiments.on('visitor:ready', (ctx) => {
  // Personalize for visitors in Austin when location is available.
  if (ctx.geo?.country === 'US' && ctx.geo.city === 'Austin') {
    showAustinOffer();
  }
});

If you are attaching listeners after the runtime has already loaded, call window.ours_experiments.getVisitorContext() directly.


Use Cases

Geographic compliance messaging: Show state-specific disclaimers or pricing based on ctx.geo?.country_region.

Market-specific offers: Target a ZIP code or DMA with ctx.geo.zip and ctx.geo.dma, checking that the values are available before using them.

Campaign-matched landing pages: Adjust hero copy to match the ad the visitor clicked, using ctx.utm.source and ctx.utm.campaign.

Returning visitor recognition: Show a loyalty message or skip the intro for returning visitors using ctx.visitor_status.

Analytics enrichment: Push visitor geo, UTM, and device data into your analytics tool or GTM dataLayer.

Conditional rendering: Show or hide page sections based on any combination of visitor signals: device type, location, behavior history.


Next Steps

How is this guide?

On this page