Permutive
Description
Record Web integrates the Permutive audience segmentation SDK to track page views and user identity. Permutive enables contextual targeting and audience profiling based on user behavior.
Permutive tracking is disabled by default. It activates only when NEXT_PUBLIC_PERMUTIVE_API_KEY is set.
Setup
The client-supplied Web key is marked Public and has been provisioned as the
NEXT_PUBLIC_PERMUTIVE_API_KEY GitHub Environment secret in DEV, UAT, and PROD.
For local development, add the same public key to your environment:
NEXT_PUBLIC_PERMUTIVE_API_KEY=YOUR_API_KEY
Restart the dev server. The Permutive SDK will be injected automatically.
DEV, UAT, and PROD deployments read the same variable from their corresponding GitHub Environment secret. Never use an iOS or Android private key here.
The Workspace ID is hardcoded as PERMUTIVE_WORKSPACE_ID in src/utils/constants.ts
and is used both for the SDK bundle URL and to build the per-workspace
*.partner.permutive.app CSP host. Rotating the workspace means changing only
that constant.
CSP requirements
The SDK needs more than the bundle host, and each of these fails quietly:
script-src:cdn.permutive.app(workspace bundle) andcdn.permutive.com.connect-src:api.permutive.com(event ingestion), the workspace*.partner.permutive.apphost,secure-signals.permutive.app,prmutv.co, andcdn.permutive.com. The last one is the SDK'scdnBaseUrl, used to XHR the query definitions and classification models - omit it and events still publish while no cohort is ever evaluated.worker-src 'self' blob:: the SDK builds its IndexedDB event-cache worker from a Blob URL. Omit it and the SDK logsWorker running in non-persistent modeand stops caching events across reloads.
The inline init script is rendered with the request nonce, which strict-dynamic
requires.
How it works
The Permutive SDK init snippet is loaded in src/app/layout.tsx via next/script with strategy="beforeInteractive", gated on NEXT_PUBLIC_PERMUTIVE_API_KEY. Loading it beforeInteractive guarantees the window.permutive stub exists before React effects (the tracking/identity hooks) run.
Two hooks are called from NavigationContext:
usePermutiveTracking(pathname)— starts or resets the official Permutivewebadd-on on every pathname change. The add-on creates the standardPageviewfields and engagement lifecycle; RecordPlus adds only the workspace-specificplanoproperty (active plan name orsem_plano).usePermutiveIdentity()— callspermutive.identify([{ tag: 'recordID', id, priority: 0 }])on login andpermutive.identify([])on logout.idis Record's internal account id: the deployment guide forbids public ids and cleartext emails as aliases, and warns that an alias mapping cannot be reversed once made.priorityis the guide's 0-99 reliability scale (0 = highest);recordIDis stable per user, so 0.
usePermutiveIdentity() is deliberately called before usePermutiveTracking()
in NavigationContext: the guide requires identify just before addon, and
effects fire in hook-call order.
The events of the approved shared map are dispatched through
trackMarketingEvent (or trackMarketingEcommerceEvent), covering
authentication, content-selection, search, subscription, and player-engagement
events. Both providers receive the same event name and fields; GA4 reads them
from the GTM dataLayer, where ecommerce payloads are nested under ecommerce
while Permutive receives the same fields flat. A missing provider does not block
the other.
abandono_carrinho is deliberately GA4-only: it has no entry in the i-cherry
map, so Permutive has no schema for it and would reject it. Every other event in
ga4Ecommerce is mapped and goes to both providers, add_payment_info included
(map §3.3.7 "Informações de pagamento").
Pageview.plano uses sem_plano for anonymous users, per the delivered
spreadsheet. GA4's separate plano user property uses sem-plano; the two are
different vendor schemas and are intentionally not unified.
plano blocks every Pageview until it is published
plano is not in the Pageview schema of Permutive's own "Record TV - Web
Deployment" guide, whose documented properties are the editoria_n1-n6,
contextual, tail, login_obrigatorio and tipo_de_conteudo family. The guide
states that a property which is not available must not be sent at all, and
Permutive rejects a Pageview carrying an undeclared property outright - so while
the extension is unpublished the workspace accepts no Pageviews, not merely a
degraded one (HTTP 400, RECORD-2269).
Until Permutive publishes the extension there are two options, and it is a product
call which to take: leave plano in place and accept zero accepted Pageviews, or
drop it temporarily to get Pageviews flowing without plan segmentation. Nothing in
this repo can resolve it.
Validation
- Deploy the target branch to DEV with the
NEXT_PUBLIC_PERMUTIVE_API_KEYGitHub Environment secret present. - Open browser DevTools, select Network, and filter for
permutive. - Navigate between Home, Search, a program detail, and the player. Confirm one
accepted
Pageviewper route, the current URL, and the expectedplano. - Log in and out. Confirm the
recordIDidentity is set and then cleared. - Exercise search, program selection, player play/pause/start, and the
subscription funnel. Confirm Permutive receives the same canonical names and
payloads as GA4, including a real ecommerce
items[]array. - In the Permutive dashboard, verify the DEV events and identity before promoting to UAT/PROD. Permutive Technical Services should validate the staging deployment before production rollout.
The delivered spreadsheet contains a Pageview.plano schema extension. If the
SDK reports a rejected Pageview, confirm that this extension has been published
to the workspace schema before changing the implementation.
Do not capture or paste API keys in QA evidence.