Personalization
Edge web personalization: A/B variant blocks served to your website by a lightweight snippet, with Wilson-scored stats and one-click winner promotion.
Personalization
Personalization is Pact's edge web experimentation surface. You define a block on your own website (a hero headline, a CTA, a pricing banner), give it two or more variants, and Pact decides which variant each visitor sees — then measures which one converts. A lightweight JavaScript snippet on your site does the fetching and event reporting; everything else lives in Pact.
Live, with a real backend
Blocks, variants, targeting rules, exposures, conversions, and Wilson-scored
stats are all real and enforced. The admin routes derive tenant_id from the
auth context; the public serve/record routes are authenticated by a per-tenant
HMAC write key. Data lives in personalization_blocks, _variants, _rules,
_exposures, and _conversions.
The moving parts
- Blocks — a named slot on your site.
GET/POST /v1/personalize/blocks,PATCH /v1/personalize/blocks/{id}(update / archive). - Variants — the competing versions.
POST /v1/personalize/blocks/{id}/variants. A default variant is auto-created so the snippet always renders something. - Rules — targeting predicates that decide who is eligible for a block.
POST /v1/personalize/blocks/{id}/rules; resolved server-side bycore.personalization.rules_engine.resolve_block. - Stats —
GET /v1/personalize/blocks/{id}/statsreturns Wilson lower-bound conversion intervals per variant, so a variant isn't declared a winner on a handful of lucky clicks. - Promote —
POST /v1/personalize/blocks/{id}/promotemakes the winner the new default and archives the losing rules in one call.
How a page view flows
visitor loads your page
│
▼
p.js snippet ──GET /v1/personalize/serve?host=…&visitor=…──▶ resolved variant
│ (rules + variant pick)
▼
renders variant, fires POST /v1/personalize/expose
│
(visitor converts)
▼
└──────────────── POST /v1/personalize/convert
The public serve, expose, and convert endpoints take no bearer token —
they run on your visitors' browsers. Instead they are authenticated by a
per-tenant write key derived by HMAC in core.personalization.signing. A
cross-tenant attempt fails signature verification with a 401, and rotating the
signing secret invalidates every active snippet at once. Visitor identifiers and
user agents are hashed (hash_visitor_id, hash_user_agent) before storage.
AI-suggested variants
POST /v1/personalize/blocks/{id}/ai-variants returns AI-drafted variant copy
for a block's prompt. When an LLM provider is configured it calls it; when none
is wired it falls back to a deterministic heuristic so the button is never broken
in dev. Suggestions are returned without being persisted — the editor previews
them and the marketer chooses which to save.
Requires the marketing module
Every personalization route is gated behind require_module("marketing"). The
surface is unavailable to tenants without that module enabled.