Skip to main content

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.

info

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) and cdn.permutive.com.
  • connect-src: api.permutive.com (event ingestion), the workspace *.partner.permutive.app host, secure-signals.permutive.app, prmutv.co, and cdn.permutive.com. The last one is the SDK's cdnBaseUrl, 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 logs Worker running in non-persistent mode and 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 Permutive web add-on on every pathname change. The add-on creates the standard Pageview fields and engagement lifecycle; RecordPlus adds only the workspace-specific plano property (active plan name or sem_plano).
  • usePermutiveIdentity() — calls permutive.identify([{ tag: 'recordID', id, priority: 0 }]) on login and permutive.identify([]) on logout. id is 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. priority is the guide's 0-99 reliability scale (0 = highest); recordID is 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

  1. Deploy the target branch to DEV with the NEXT_PUBLIC_PERMUTIVE_API_KEY GitHub Environment secret present.
  2. Open browser DevTools, select Network, and filter for permutive.
  3. Navigate between Home, Search, a program detail, and the player. Confirm one accepted Pageview per route, the current URL, and the expected plano.
  4. Log in and out. Confirm the recordID identity is set and then cleared.
  5. 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.
  6. 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.