ابرووا
شروع رایگان
☰ فهرست مستندات · شروع
به‌زودی. Abrova A/B Testing به‌زودی فعال می‌شود. این صفحه توضیح می‌دهد چطور کار خواهد کرد؛ هنوز قابل استفاده نیست.
این صفحه هنوز ترجمه نشده — نسخه‌ی انگلیسی نمایش داده می‌شود. English

Abrova A/B Testing

A/B experiments, feature flags, event tracking, offline sync, mutual exclusion, and holdout groups. SDKs for Android, iOS, Flutter, React Native, Node.js, and Next.js.

به‌روزرسانی: ۵ آبان ۱۴۰۵
What is Abrova A/B Testing?

Run controlled experiments, ship features behind flags, and measure conversion impact with statistical significance. The Abrova A/B Testing platform includes experiments, feature flags, event tracking, offline sync, mutual exclusion groups, holdout groups, and webhooks.

Using an AI assistant? See AI assistants for how to give it these docs.

What it will cost.

2,000 participants are free every month. The price above that is set at launch and will be paid from your prepaid wallet. See Pricing.

Quick Start#

Follow these steps to integrate Abrova A/B Testing into your app:

1. Install SDK

Add the Abrova Center registry, then install the SDK (version 0.2.3 or later)

2. Add a token endpoint

Your server mints a user token for the signed-in user

3. Initialize

Configure with your API key and a tokenProvider

4. Get variants & track

Fetch variants, then log views and conversions

Platform SDKs#

Select your platform to view detailed installation and usage instructions.

Android

Native Kotlin SDK for Android apps

iOS

Native Swift SDK for iOS apps

Flutter

Cross-platform Dart SDK for Flutter apps

React Native

Cross-platform SDK for React Native apps

Next.js

Client-side SDK for Next.js / React web apps

Node.js

Server-side SDK for Node.js backends

User tokens#

Your API key identifies your project, not your user. It ships inside every app and web bundle, so anyone can read it. To know which user a request is for, your server issues a short-lived user token and the SDK sends it with every request.

CredentialLives inProves
API key (ab_live_…)Your app, publicWhich project
Server secret (ab_srv_…)Your server onlyThat your backend is calling
User token (1 hour by default)Your app, per userWhich user

A user token is required for everything that acts for a user: getting a variant, tracking events and conversions, and evaluating feature flags. Loading the experiment and flag lists works with the API key alone, so your app can do that before anyone signs in.

One token for every Abrova product

Mint the token on your server from the Abrova auth endpoint:

bash
curl -X POST https://auth.abrova.ir/users/token \
  -H "x-api-key: $ABROVA_API_KEY" \
  -H "x-server-secret: $ABROVA_SERVER_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"userId": "user-123", "expiresIn": 3600}'

# → { "token": "eyJhbGciOi…", "userId": "user-123", "expiresIn": 3600, "expiresAt": "2026-09-27T10:00:00.000Z" }

expiresIn is in seconds: 3600 (1 hour) by default, 86400 (24 hours) maximum. The same token works for Abrova A/B Testing, Chat, Sync and Push — give it to every SDK's tokenProvider instead of fetching one per product. Using Node.js? createUserToken(userId) in the Node.js SDK does this call for you.

The SDKs call tokenProvider when they need a token, again shortly before it expires, and once more if the service reports it expired.

The token decides the user

The service takes the user from the signed token, not from the app, and the SDKs follow it: once an SDK has a token, it acts as the user that token was minted for. If you also call setUserId, pass the same ID your server puts in the token. On React Native and Next.js you may skip setUserId altogether.

Before your user signs in

You can initialize a client SDK before anyone is signed in. Have tokenProvider return null while there is no user (on iOS, complete with .failure). From version 0.2.2 the SDK then waits for a user token instead of failing:

  • getVariant returns the default you pass. Flags return your default or, on Android, iOS and Flutter, the answer from the cached flag list.
  • Nothing is sent for a user, and no error is reported.
  • Events you track are kept on the device, up to 1,000.

isAwaitingUserToken is true while the SDK waits, and onAwaitingUserToken is called each time it starts waiting. When a token arrives, the SDK carries on by itself: it fetches the variants your app asked for and sends the kept events.

Tell the SDK when the user signs in or out. The calls have the same names in every client SDK:

WhenWith a tokenProviderHanding tokens over yourself
After sign-inrefreshUserToken()setUserToken(token)
After sign-outclearUserId()setUserToken(null) (nil on iOS)

Events are credited to the user in the token. Events recorded before sign-in are only sent if they were recorded for the same user ID that signs in (you called setUserId with that ID before tracking). Events recorded for an anonymous user are not credited to anyone and are dropped at sign-in.

Signing out and switching users

A sign-out, a setUserId call with a different ID, or a token for another user clears the previous user's variants, attributes, token and pending events before anything is done for the next user. Events of the previous user that are ready to send go out under that user's own token, never under another user's.

Signed-out visitors need a token too if you want them in an experiment. Either have your server mint one for an anonymous ID (for example a device or session ID you pass to setUserId), or run experiments only after sign-in. Without a token, the service records no assignments or events for that user.

Keep it on the device.

A token is valid until it expires, so store it with the user's session (Keychain, Keystore or secure storage) and reuse it across app launches. Your app then only calls your backend when the token is close to expiry — not on every start.

Never ship the server secret.

The server secret stays on your server. Never put it in an app, a browser bundle, or a NEXT_PUBLIC_ variable. Server-side code (the Node.js SDK) uses the server secret directly instead of a user token.

All Features#

Abrova A/B Testing provides a complete experimentation platform with these features.

A/B Testing

Run controlled experiments to optimize user experience

Offline Support

Queue events when offline, sync automatically when back online

Feature Flags

Toggle features on/off without deploying code

Mutual Exclusion

Prevent users from being in conflicting experiments

Holdout Groups

Measure cumulative impact of your experimentation program

Real-time Analytics

Track conversions, views, and custom events

Webhooks

Get notified when experiments start, pause, complete, or are archived

Signed User Tokens

One token per user, shared with Abrova Chat, Sync and Push

A/B Testing#

Run controlled experiments to test different variations of your app and measure which performs better.

How it works

  1. Create an experiment in the dashboard with multiple variants (e.g., control, variant-a, variant-b)
  2. Set traffic allocation — decide what percentage of users should be in the experiment
  3. Get variant in your app — the service decides which variant the user should see, and the SDK returns it and remembers it
  4. Track events — log views, clicks, and conversions
  5. Analyze results — see which variant performs better with statistical significance

Pseudocode Example

The basic experiment loop, in any client SDK (see each platform page for exact syntax):

typescript
// 1. Initialize with your API key and a token provider
AbrovaAbTesting.init({
  apiKey: "YOUR_API_KEY",
  tokenProvider: () => fetchAbrovaTokenFromYourServer(),
})

// 2. Identify the signed-in user (on logout, call clearUserId())
AbrovaAbTesting.setUserId("user-123")

// 3. Get variant for the current user
const variant = await AbrovaAbTesting.getVariant(
  "checkout-button-color",
  "control"  // default until the service has assigned a variant
)

// 4. Apply variant in your UI
// Android and iOS return the variant key ("variant-a"); Flutter, React Native,
// Next.js and Node.js return the variant name from the Console ("Variant A")
if (variant === "variant-a") {
  button.color = "green"
  button.text = "Buy Now!"
} else if (variant === "variant-b") {
  button.color = "blue"
  button.text = "Add to Cart"
} else {
  button.color = "gray"      // control
  button.text = "Purchase"
}

// 5. Track when user views the experiment
AbrovaAbTesting.trackView("checkout-button-color")

// 6. Track conversion when user completes action
AbrovaAbTesting.trackConversion(
  "checkout-button-color",
  300000  // optional: revenue value
)

Variants are decided by the service only. When the service can't be asked (no network, or no user token yet), the SDKs return the assignment they cached earlier, or your default. No SDK works a variant out on the device.

On Android and iOS, the synchronous getVariant never blocks: it returns the stored variant, or your default while the assignment loads. Use getVariantAsync (Android) or the completion form (iOS) the first time a screen needs a variant, so the user doesn't briefly see the default.

A/B Testing Best Practices.
  • Always track views to accurately measure conversion rates.
  • Run experiments for at least 2 weeks to account for weekly patterns.
  • Aim for statistical significance (95%+) before making decisions.
  • Only test one thing at a time for clear results.

Offline Support#

Abrova A/B Testing SDKs support offline mode, so your app keeps tracking events without a network connection.

How It Works.
  • Events are stored on the device while offline
  • Automatic sync when network connectivity is restored
  • Events persist across app restarts
  • Each event carries a unique clientEventId, saved with the event
  • Each event carries the time the app recorded it, so events sent together keep their order
  • At most 1,000 events are kept on the device
  • The SDKs send events in batches, and wait before retrying when the service is busy or your project has reached its usage limit
  • The sync endpoint accepts up to 500 events per request

List cache (default 300 seconds) and send interval (default 30 seconds) are set in the Console; the SDKs keep up to 1,000 events and send 100 at a time.

Send interval and list cache

From version 0.2.3, every SDK follows two of your project's settings in the Abrova console (A/B Testing › Settings › Defaults):

  • Send interval — how often queued events are sent. The SDKs follow it unless you set your own flushInterval in code; then your value wins. Each platform page says how to set it.
  • List cache — how long an SDK reuses the experiment list before it asks the service again. There is no SDK option for it. Once the list is older than that, the SDK fetches it again in the background the next time your code reads a variant or tracks an event, without blocking the call.

A change you make in the console (a new experiment, changed weights or traffic) reaches running apps within the list cache time, without a restart. A user who already has a variant keeps it. A changed send interval is picked up at that same refresh. The SDKs keep both values within safe limits. Apps on SDK versions older than 0.2.3 keep their built-in behaviour.

Benefits

  • No lost events due to network issues
  • Works in subway, airplane, rural areas
  • Reduces battery usage with batched syncs
  • Survives app crashes and restarts

Flutter

dart
import 'package:abrova_ab_testing/abrova_ab_testing.dart';

// Offline support is always on
await AbrovaAbTesting.init(AbrovaAbTestingConfig(
  apiKey: 'YOUR_API_KEY',
  tokenProvider: fetchAbrovaToken, // returns a token from your server, or null while signed out
));
await AbrovaAbTesting.instance.setUserId('user-123');

// Events are queued automatically when offline
await AbrovaAbTesting.instance.trackConversion('checkout-flow', value: 1000000);

// Send queued events now (optional - automatic sync runs on the send interval)
await AbrovaAbTesting.instance.flush();

Android

kotlin
import ir.abrova.abtesting.AbrovaAbTesting
import ir.abrova.abtesting.AbrovaAbTestingConfig
import ir.abrova.abtesting.AbrovaTokenProvider

// Offline support is enabled by default
AbrovaAbTesting.init(
    context = applicationContext,
    config = AbrovaAbTestingConfig(
        apiKey = "YOUR_API_KEY",
        tokenProvider = AbrovaTokenProvider { myBackend.fetchAbrovaTokenBlocking() }
    )
)

// Events are queued automatically when offline
AbrovaAbTesting.trackConversion("checkout-flow", 1000000)

// Send queued events now (optional - automatic sync runs periodically)
AbrovaAbTesting.flush()

REST API

For custom SDK implementations, use the sync endpoint to batch upload events. Send the user's token in x-user-token; the events are recorded for the user the token names.

bash
curl -X POST 'https://abtest.abrova.ir/public/sync' \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'x-user-token: USER_TOKEN' \
  -d '{
    "events": [
      {
        "experimentId": "checkout-flow",
        "variantId": "",
        "userId": "user-123",
        "eventType": "conversion",
        "eventName": "purchase",
        "eventValue": 1000000,
        "metadata": { "product": "premium" },
        "timestamp": 1790505000000,
        "clientEventId": "client-event-abc123"
      }
    ],
    "deviceId": "device-xyz",
    "sdkVersion": "custom-1.0.0"
  }'

# Response:
{
  "success": true,
  "synced": 1,
  "failed": 0
}

experimentId accepts the experiment key. Leave variantId empty to use the variant the user is assigned to.

timestamp is optional: the time your app recorded the event, in epoch milliseconds (an ISO 8601 string is also accepted). It keeps the events of one request in the order they happened. An event without it is still recorded.

SDK Init Endpoint.

Use the /public/init endpoint to fetch experiments and configuration in a single call. It returns the project's running experiments and the list cache and send interval set in the Console. It needs only the API key.

Feature Flags#

Toggle features on or off without deploying new code. Perfect for gradual rollouts, kill switches, and beta features.

Use Cases

  • Gradual Rollouts — Release to 5% users, then 25%, then 100%
  • Kill Switches — Instantly disable problematic features
  • Beta Features — Enable for specific user segments
  • Seasonal Features — Toggle holiday themes on/off

Flag Properties

  • Key — Unique identifier (e.g., new-checkout-flow)
  • Enabled — Whether the flag is on or off
  • Rollout % — Percentage of users who see the feature
  • Targeting — Rules based on user attributes

Pseudocode Example

typescript
// Check if a feature is enabled for the signed-in user
const isEnabled = await AbrovaAbTesting.isFeatureEnabled("new-checkout-flow")

if (isEnabled) {
  showNewCheckoutFlow()
} else {
  showLegacyCheckout()
}

// Get a feature flag value (for multivariate flags)
const value = await AbrovaAbTesting.getFeatureValue(
  "button-color",
  "blue"  // default value
)
button.color = value

// Feature flags with user targeting
// In dashboard, set rules like:
// - Enable for users where plan = "premium"
// - Enable for users where country in ["US", "CA"]
// - Enable for 10% of all other users
Dashboard Setup.
  1. Open Abrova Flags in the sidebar and go to Feature Flags (the A/B Testing Feature Flags page links there)
  2. Click Create Flag
  3. Enter a key (e.g., new-checkout-flow) and name
  4. Set rollout percentage (0-100%)
  5. Check Enable immediately to turn it on

Mutual Exclusion Groups#

Prevent users from being enrolled in multiple conflicting experiments at the same time.

Why Use Mutual Exclusion?

If you're running two experiments that both affect the checkout flow, a user being in both could skew your results. Mutual exclusion ensures clean, uncontaminated experiment data.

Example Scenario

You have two checkout experiments:

  • Experiment A: Button color test
  • Experiment B: Checkout flow redesign

If a user is in both, you can't tell which change caused the conversion.

How It Works

  1. Create an exclusion group
  2. Add experiments from the same project to the group
  3. Set total traffic allocation for the group
  4. Each user is assigned to ONE experiment in the group

Example

typescript
// Exclusion Group: "Checkout Experiments"
// Traffic Allocation: 100% (all users eligible)
// Experiments in group:
//   - checkout-button-color
//   - checkout-flow-redesign

// A user is placed in only one of them - never both.

// In your app, just call getVariant normally:
const variant = await AbrovaAbTesting.getVariant("checkout-button-color")
Dashboard Setup.
  1. Go to Exclusion Groups in the sidebar
  2. Click Create Group
  3. Name your group (e.g., "Checkout Experiments")
  4. Set traffic allocation (% of users eligible for this group)
  5. Select which experiments belong to this group

Holdout Groups#

Measure the cumulative impact of your entire experimentation program by excluding a percentage of users from ALL experiments.

What is a Holdout Group?

A holdout group is a percentage of users (typically 5-10%) who never see any experiments. They always get the control experience. By comparing the holdout group to users who participate in experiments, you can measure the overall value of your experimentation program.

Holdout Users

Always see the control experience. Never enrolled in any experiment.

Experiment Users

Participate in experiments as normal. May be in control or variant groups.

Comparison

Settings in the Console shows how many users are in the holdout and how many are in experiments.

In your app

Nothing changes in your code. For a holdout user, getVariant() returns the control variant for every experiment, so they see the default experience:

typescript
const variant = await AbrovaAbTesting.getVariant("checkout-experiment")
applyVariant(variant) // the control variant for holdout users

Tracking calls are always safe to make: you don't need to check whether the user is in an experiment before you track. The service decides what counts, and events of a user who is kept out of an experiment never enter that experiment's results.

Dashboard Setup.
  1. Go to Settings in the sidebar
  2. Find Holdout Group Configuration
  3. Toggle Enable Holdout Group
  4. Set percentage (recommended: 5-10%)
  5. Click Save Holdout Settings

Webhooks#

Get notified in real time when an experiment changes state. Each webhook belongs to one project and receives only that project's events.

Events

  • experiment.started — Experiment goes live
  • experiment.paused — Experiment paused
  • experiment.completed — Experiment ends
  • experiment.archived — Experiment archived

Use Cases

  • Post to your team chat when experiments start or end
  • Trigger CI/CD pipelines when an experiment completes
  • Update external systems with experiment status

Webhook Payload Example

text
POST https://your-server.com/webhooks/abrova-ab-testing
Content-Type: application/json
X-AbrovaAbTesting-Event: experiment.completed
X-AbrovaAbTesting-Webhook-Id: 7c1f…
X-AbrovaAbTesting-Timestamp: 2026-09-27T10:30:00.000Z
X-AbrovaAbTesting-Signature: 5d41402abc4b2a76b9719d911017c592…

{
  "event": "experiment.completed",
  "timestamp": "2026-09-27T10:30:00.000Z",
  "webhookId": "7c1f…",
  "experimentId": "…",
  "experimentKey": "checkout-button-color",
  "experimentName": "Checkout Button Color",
  "status": "completed",
  "endDate": "2026-09-27T10:30:00.000Z"
}

Verifying the signature

When you set a secret, X-AbrovaAbTesting-Signature is the hex HMAC-SHA256 of the raw request body, keyed with your secret:

typescript
import { createHmac, timingSafeEqual } from 'crypto';

function isValid(rawBody: string, signature: string, secret: string) {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
  return expected.length === signature.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Webhook URLs must be public http(s) addresses. Failed deliveries are retried.

Dashboard Setup.
  1. Pick your project in the sidebar and go to Webhooks
  2. Click Create Webhook
  3. Enter your endpoint URL
  4. Select which events to receive
  5. (Optional) Add a secret for signature verification
  6. Click Test to verify it works

REST API#

Use the REST API directly for platforms without an SDK. Calls that act for a user need the user's token in x-user-token (see User tokens above); from your own server you can send x-server-secret instead.

Get Variant Assignment

bash
curl -X POST 'https://abtest.abrova.ir/public/assign' \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'x-user-token: USER_TOKEN' \
  -d '{
    "experimentKey": "checkout-button-color",
    "userId": "user-123",
    "userAttributes": {
      "plan": "premium",
      "country": "US"
    }
  }'

# Response:
{
  "data": {
    "experimentId": "…",
    "experimentKey": "checkout-button-color",
    "variantId": "…",
    "variantKey": "variant-a",
    "variantName": "Variant A",
    "config": { "buttonColor": "#22C55E", "buttonText": "Buy Now" },
    "isControl": false,
    "isNew": true
  }
}

Track Events

Send events with /public/sync (see Offline Support above). It accepts experiment keys and up to 500 events per request.

Evaluate a Feature Flag

bash
curl -X POST 'https://abtest.abrova.ir/public/flag-evaluation' \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'x-user-token: USER_TOKEN' \
  -d '{
    "flagKey": "new-checkout-flow",
    "userId": "user-123",
    "userAttributes": {
      "plan": "premium"
    }
  }'

# Response:
{
  "flagKey": "new-checkout-flow",
  "enabled": true,
  "value": "variant-b"
}

Errors

A request that acts for a user without a valid token is refused with 401 and a code:

  • token_required — no user token was sent
  • token_expired — the token expired; fetch a new one and retry
  • token_invalid — the token is not valid for this project

Where to next?#

  • Platform SDK docs — Android, iOS, Flutter, React Native, Next.js, Node.js — for install + code examples
  • Feature Flags SDK — standalone feature flags package if you don't need experiments

All Abrova A/B Testing SDKs share the same dashboard, the same API key, and the same user token. Pick the SDK for your platform and you're up and running in minutes.

این صفحه مفید بود؟
گزارش مشکل