What leaves your server

Evolve runs experiments inside your Next.js app. Visitors' exposures and events are written to your own Postgres (an evolve schema). Evolve Cloud never connects to that database: it sends your app a signed request, your app runs the counting query itself and answers with totals. This page lists everything your app sends to Evolve Cloud, field by field, and everything Evolve fetches.

Never leaves your server

  • Visitor ids (the evolve_id cookie) or user ids
  • Raw events, exposures or page views
  • IP addresses, user agents, referrers of your visitors
  • Event props and their values (only totals of goal values)
  • Your local-fn goal functions
  • Database credentials (EVOLVE_DATABASE_URL stays in your env)

Manifest (configuration)

Your app's Evolve configuration: which routes are tested, your goal definitions, versions and the public URL Evolve calls back on.

  • How: Your app → POST /v1/manifest, with your secret key
  • When: When the app boots, and on npx next-evolve push
  • Kept: Stored on the site (routes, goals, versions, app URL) until you delete the site.
FieldWhat it is
pvProtocol version.
sdkVersionnext-evolve version.
protocol.supportedProtocol versions the SDK speaks.
nextVersionNext.js version.
routesRoute paths that hold optimizable regions (keys).
routes{}.mode"static" or "dynamic" rendering.
goalsGoal keys from evolve.config.ts.
goals{}.kind"event" or "pageview".
goals{}.eventEvent name the goal counts (event goals).
goals{}.sourceOptional event source label (event goals).
goals{}.whereFilters on event props as written in your config (literal strings, numbers, booleans). Don't put personal data in goal filters.
goals{}.pathPage path or prefix (pageview goals).
goals{}.valueHow a value is read: none, a prop path, or a local function.
goals{}.value.kind"prop" or "local-fn" (the function itself never leaves).
goals{}.value.pathDotted prop path, e.g. order.total.
goals{}.windowAttribution window, e.g. 7d.
goals{}.unit"visitor" or "user": the unit counted. Not an id.
goals{}.direction"increase" or "decrease".
hashHash of routes + goals.
appUrlYour app's public base URL (webhooks and aggregate requests).

Region schemas and control copy

What a region can change and what it shows today, so the dashboard and AI can propose variants.

  • How: Your app → POST /v1/regions, with your secret key
  • When: The first time each <Optimizable> region renders on a page (and when its schema changes)
  • Kept: Stored per region until you delete the site.
FieldWhat it is
pvProtocol version.
idRegion id you chose (e.g. hero).
jsonSchemaJSON Schema of the region's editable props (from your Zod schema).
schemaHashHash of that schema.
controlPropsThe control copy: the current values of the schema's props, as rendered. Public page content, not visitor data.
pathThe page path the region rendered on.
varsNames of the per-page placeholders the region offers there, e.g. name or city (the route's params and its vars prop's keys). Names only, never their values.

Aggregate answers (sufficient statistics)

Your app runs the counting query on its own Postgres and returns totals per experiment, variant, goal, segment and day. No row is about one visitor.

  • How: Your app → the signed answer to Evolve's signed request to <appUrl>/api/evolve/aggregate
  • When: Every few minutes to daily while an experiment runs (your plan's cadence), and on Recompute
  • Kept: Stored as daily buckets and computed results until you delete the site.
FieldWhat it is
pvProtocol version.
requestIdEcho of Evolve's request id.
siteYour site id.
asOfThe cut-off time of the counts.
sdknext-evolve version.
mode"incremental" or "full".
durationMsHow long the query took.
partialWhether the time budget ran out.
remainingExperiment ids not answered this time.
rows[].expExperiment id.
rows[].varVariant id.
rows[].goalGoal key.
rows[].segSegment (all traffic, or a rule id).
rows[].dayCohort day (UTC).
rows[].nVisitors exposed (a count).
rows[].cVisitors who converted (a count).
rows[].s1Sum of goal values.
rows[].s2Sum of squared goal values.
rows[].mnExposed visitors whose goal window has passed (a count).
rows[].mcOf those, conversions (a count).
rows[].ms1Their sum of values.
rows[].ms2Their sum of squared values.
usage[].dayDay (UTC).
usage[].testedPageviewsTested page views that day (a count, for plan usage).
errors[].expExperiment a query failed for.
errors[].goalGoal a query failed for.
errors[].messageThe error text (at most 500 characters), e.g. a timeout or a missing table. Never row data.
vitals[].expExperiment id (Core Web Vitals).
vitals[].varVariant id.
vitals[].metriclcp, cls or inp.
vitals[].dayDay (UTC).
vitals[].binsHistogram: how many page views fell in each fixed range. Counts only.
pages[].expExperiment id (pattern-route experiments only).
pages[].nHow many distinct pages exposed it (a count, for “pooled across N pages”). Never which pages.

App URL

Where Evolve sends sync webhooks and aggregate requests.

  • How: npx next-evolve init → PUT /v1/site/app-url, with your secret key
  • When: Once, at setup (the manifest can also carry it)
  • Kept: Stored on the site.
FieldWhat it is
appUrlYour app's public base URL.

Bundle polling and CLI checks

These calls carry your secret key and no body: Evolve sends your signed experiment bundle back. Evolve records when the bundle was last fetched.

  • How: Your app → GET /v1/bundle; the CLI → GET /v1/site, POST /v1/site/test, POST /v1/aggregates/request
  • When: Every 60 s per app instance, and when you run CLI commands
  • Kept: Last fetch time, and status codes of recent calls (Connect page).

Public page text (for AI suggestions)

Evolve fetches your public page like any visitor, as EvolveBot: no cookies, no login, and your SDK serves it control and records nothing. It keeps the visible text, at most 4,000 characters.

  • How: Evolve → GET <appUrl><path>, anonymously
  • When: When you ask for AI suggestions or a brand profile (cached 24 h)
  • Kept: The page text and fetch time per path, until refreshed or the site is deleted. Prompts built from it are kept as AI run records.

What comes back to your app

  • The signed bundle (GET /v1/bundle): which experiments run on which routes, each variant's props and weights. Your SDK verifies Evolve's signature.
  • Sync webhooks (POST <appUrl>/api/evolve/sync): "a new bundle is ready", HMAC-signed.
  • Aggregate requests (POST <appUrl>/api/evolve/aggregate): which experiments, variants and goals to count, HMAC-signed.

Retention and deletion

  • In your Postgres: yours to set. retentionDays in evolve.config.ts drops old events locally; Evolve can't see or delete them.
  • In Evolve Cloud: kept while the site exists. Delete site (Site → Settings) stops the site at once: your app gets an empty bundle and serves control, the key stops working, and all of the site's cloud data is deleted within 7 days. A record that the site was deleted (id, who, when, rows removed) stays with your org.
  • Request metadata: our hosting provider keeps standard request logs (your server's IP address, path, status) for a short period.

Support access (beta)

During the beta, Evolve support may access your account to help you, for example to look at a failing aggregate. Every such access is logged and visible in your audit log.