VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL

Feature flags and A/B tests

Read feature flags in your app or on your server, and measure A/B tests on paywalls.

A feature flag turns a feature on or off, or picks a variant, for each person without a release. An A/B test splits the people who reach a paywall location between several paywalls and measures which converts better. Your code reads flags and decides what to do; A/B tests on paywalls need no code at all.

Early access

Feature flags and A/B tests are in early access. Ask us on Discord, or email support@voidhash.com on a paid plan, to turn them on for your team.

UseWhen you want to
A feature flagRoll out a feature gradually, switch it off remotely, or try variants of your own screens.
An A/B testCompare paywalls at a location and read conversion and revenue per arm in Studio.

Don't gate paid access on a flag. Check perks for that.

Evaluate flags in your app

Each SDK evaluates flags for the current person, caches the results on the device and keeps answering from the cache while Voidhash is unreachable. A flag that is off, unknown or not evaluated yet reads as off, or as the default you set, so write your code so that off is the safe behavior. Changes made in Studio reach the app at its next refresh.

See the SDK pages for Swift, Kotlin, React Native and Web. Flags work without analytics consent.

Evaluate flags on your server

Call POST /projects/{projectId}/feature-flags/evaluate with a secret key. Name the person by any of personId, distinctId, email or externalIds, and list the flag keys you need, or omit them for every flag:

curl -X POST "https://api.voidhash.com/api/v1/projects/$VOIDHASH_PROJECT_ID/feature-flags/evaluate" \
  -H "Authorization: Bearer $VOIDHASH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "distinctId": "user_123", "keys": ["new-checkout"] }'

Each entry in flags has key, isEnabled, variantKey, the variant's value, and the reason for the answer, from the rules below. The call changes nothing and is safe to repeat. It answers as the person's app would.

How a flag decides

For each person, Voidhash checks these rules in order and stops at the first match:

OrderRuleResultreason
1The flag is disabled.Off, overrides included.disabled
2An override exists for the person.The overridden value.override
3The person is on the deny list.Off.denied
4The flag has an allow list without the person.Off.not_allowed
5No identity is known for the person.Off.no_identity
6The person's bucket is outside the rollout.Off.rollout
7Otherwise: on, with the person's variant for a flag with variants.On.rollout

People on the allow list still go through the rollout, so a 0% rollout gives them nothing; use an override to guarantee someone the flag. Archived flags aren't evaluated at all, and apps fall back to their defaults. Traffic splits evenly between a flag's variants.

Set what Studio can't

Studio sets the rollout only for Boolean flags, and its overrides only force a flag on or off. Use the API, CLI or MCP server for the rest:

  • Rollout of a string, number or JSON flag: send rolloutPercentage (0–100, in 0.01 steps) to PATCH …/feature-flags/{featureFlagId}, or run voidhash flag rollout button-color 100%.
  • Pin a person to a variant: send isEnabled, variantKey and an optional note to PUT …/feature-flags/{featureFlagId}/overrides/{identity}, where identity is distinct_id:user_123 or another identity type. A pinned variant turns the flag on. The CLI equivalent is voidhash flag override button-color user_123 blue --note "QA".
curl -X PUT "https://api.voidhash.com/api/v1/projects/$VOIDHASH_PROJECT_ID/feature-flags/$FLAG_ID/overrides/distinct_id:user_123" \
  -H "Authorization: Bearer $VOIDHASH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "isEnabled": true, "variantKey": "blue", "note": "QA" }'

How assignment stays stable

Voidhash assigns flags and A/B test arms by the person, not at random on each request, and stores nothing per person to do it:

  • The same person gets the same answer on every visit and, once identified, on every device where your app identifies them with the same user ID.
  • Raising a rollout only adds people. Nobody who has a flag loses it when you go from 10% to 20%.
  • Identifying can change the answer. A person who starts anonymous and then signs in is assigned again as the identified person, so their flag variant or test arm may change once.
  • Editing an A/B test's split can move people. Changing weights while a test is paused may put some people in another arm when it resumes.

Flags have no environments: the same flag answers in production, sandbox and test.

A/B tests on paywalls

An A/B test runs at one or more locations. Your app keeps calling presentFlow with the same slug, and Voidhash picks the person's arm when the location resolves. There is no SDK method for A/B tests and nothing to release. Each arm shows its paywall's current deployed build, so an app whose SDK can't draw an arm's paywall gets FLOW_NOT_ASSIGNED and isn't counted.

Exposures

When a location resolves to an arm, Voidhash records a $experiment.exposed event for the person, with the test in experimentId and the arm in variantKey. Repeated exposures to the same arm count once. No exposure is recorded while the test is paused, or when the person has withheld analytics consent; they still see a paywall. Turning off the experiment exposure event under the project's event settings breaks results.

Conversions

A conversion is the test's goal event, sent any time after the person's first exposure. Set it explicitly when you create the test:

  • $subscription.created for a paywall that sells subscriptions. It fires when a subscription starts, including a free trial.
  • $purchase.completed for one-time products, or a mix. It fires on every new purchase, but not on renewals.
  • A custom event your app captures, for any other goal.
Always set the goal

Without a goal, a test counts purchase_completed, an event Voidhash never sends, so every arm shows 0 conversions. The goal locks once the test starts.

From the API, set primaryMetricEventName when you create the experiment, or update it while it is a draft. Revenue per arm counts what exposed people paid after their first exposure; it needs no extra events.

Results

GET …/experiments/{experimentId}/results returns each arm's exposures, conversions, conversionRate and revenue (USD cents) since the test last started or resumed. A person counts in the arm of their first exposure. Results are per environment: exposures and purchases made through a test key appear only when you read results with a test secret key, never in Studio or with a production key. Studio, the CLI and the MCP server read these counts the same way: each arm is compared with the control by a two-sided, pooled two-proportion z-test at 5% significance, split between the arms when there are several (a Bonferroni correction). An arm is too early to tell while it or the control has fewer than 100 exposed people, or both together have fewer than 10 conversions. Secondary events are recorded but not analyzed.

Experiments on your website

The web SDK doesn't present paywalls. To test something on your website, use a flag with variants and track your own event with the variant when the visitor sees it; the web SDK records no exposure when a flag is read.

Next steps

  • Manage A/B tests and manage feature flags in the Help center.
  • Paywalls in your app: locations, results and fallbacks.
  • Events: built-in revenue events and custom events.
  • Manage flags and tests from the CLI or an AI agent.

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousPaywalls in your appNextComponents

On this page

Evaluate flags in your appEvaluate flags on your serverHow a flag decidesSet what Studio can'tHow assignment stays stableA/B tests on paywallsExposuresConversionsResultsExperiments on your websiteNext steps