> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.qonversion.io/llms.txt
> Use this file to discover all available pages before exploring further.

# How targeting and assignment works

> The mechanics behind who gets which Remote Config: first-match by priority, deterministic rollout, sticky assignments, re-evaluation, and how user properties and identify interact with targeting

This page describes the evaluation model behind [Remote Configs](/docs/remote-config) — what happens on the device and on the server when your app asks for a config, and what changing targeting rules does to users who already received one.

## When a device fetches a config

Remote configs are fetched **lazily**: nothing is loaded at app start. The SDK requests configs when your code calls `remoteConfig()` / `remoteConfigList()` (or their equivalents on wrapper SDKs).

Two things to know about the fetch:

1. **Pending user properties are flushed first.** Before every remote config request the SDK force-sends any user properties waiting in its debounce queue and only then calls the config endpoint — so a config requested right after `setUserProperty(...)` is evaluated with that property already stored. The server reads properties synchronously at request time; there is no replication lag.
2. **Responses are cached in memory** per context key for the lifetime of the process. A cached config is served without a network call. The cache is dropped when:
   * the user changes (identify with a different user id, logout) — and on iOS 6.14.0+, Android 9.7.0+, and the wrapper versions listed below, on **any** `identify` call, even with the same id;
   * you call `attachUserToRemoteConfiguration` / `detachUserFromRemoteConfiguration` (or the experiment counterparts) — **all** cached configs are invalidated, including named context keys;
   * you call `invalidateRemoteConfigsCache()` explicitly (iOS 6.14.0+, Android 9.7.0+, React Native 10.11.0+, Flutter 11.10.0+, Cordova 7.9.0+, Capacitor 1.8.0+, Unity 9.9.0+).

If the network request fails, the SDK can serve a [bundled fallback file](/docs/remote-config#7-fallback-files-optional). Fallbacks are never cached — the next call retries the network.

## How one config is chosen among several

When several configurations share a context key, the server picks at most one:

1. **Experiments win first.** If the user is assigned to a group of a non-finished experiment for that context key, the experiment's payload is returned. Finished experiments release their users.
2. **Remote configurations are evaluated in priority order** — the order you set on the Remote Configs list (drag-and-drop in the dashboard). The **first** configuration whose targeting matches the user wins; users who don't match fall through to the next one.
3. For each candidate the server checks **targeting rules** (`segmentation_conditions`, including audience references) and then the **rollout percentage** (`segment_percent`).

The rollout percentage is **deterministic**: each user is bucketed by a stable hash of the configuration and their user id into 0–99 and admitted when the bucket is below the percent. Consequences:

* The same user gets the same in/out decision on every evaluation — a config edit never reshuffles who is inside the rollout.
* **Raising the percent only adds users** (the newly admitted top buckets); lowering it removes only the users in the topmost buckets.

If nothing matches, the SDK gets no config for that context key.

## Assignments: why evaluation is sticky

The first evaluation that matches a configuration is **persisted** as an assignment (attachment) for that user and context key. Later requests serve the stored assignment directly — the targeting rules are not re-run on every request. This is what makes delivery cheap and consistent: a user who saw a price or a paywall keeps seeing it.

There are two assignment types, reported in the SDK's `RemoteConfigurationSource.assignmentType`:

* **Automatic** — assigned by matching targeting rules.
* **Manual** — pinned explicitly via `POST /remote-configurations/{config_id}/users/{user_id}` or the SDK attach methods. A manual pin **overrides targeting rules and is never re-evaluated** — it survives config edits until you detach it, archive the config, or delete it.

## Freezing assignments

A configuration can be **frozen** with the *Freeze assignments* switch in the dashboard's Targeting card. While frozen, every user who **newly** receives the configuration is stored as a **manual pin** instead of an automatic assignment — so later targeting, rollout, or priority changes never re-seat them (nothing in the re-evaluation list below applies to manual pins).

* **Enabling freeze** offers a one-shot option to also pin the users who already received the configuration automatically. Skip it, and only future recipients are pinned — existing automatic assignments stay re-evaluable.
* **Disabling freeze is one-way**: pins created while it was on stay pinned (they are indistinguishable from pins you made yourself). Only new deliveries become re-evaluable again.
* **Payload edits still apply** to pinned users — freezing controls *who* keeps the config, not *what* they receive.
* To release pinned users, detach them individually or archive/delete the configuration.

Freeze-made pins report `assignmentType: manual`, exactly like your own pins.

## What re-evaluates an automatic assignment

Automatic assignments are re-evaluated when the targeting they were computed against changes. Any of these bumps the configuration's targeting epoch:

* editing the configuration's targeting rules or `segment_percent`;
* publishing or restoring a configuration version;
* reordering priorities within the context key;
* editing an **audience** referenced by the configuration's rules — this re-evaluates every configuration that references it.

On the user's **next request** after such a change, the server evaluates them against the current rules:

* still matching → they keep the configuration (nothing changes);
* no longer matching → they migrate to the next matching configuration in priority order, or to none.

<Note>
  **Gradual rollout.** Re-evaluation is being enabled project by project. On projects not yet migrated, automatic assignments are **never** re-evaluated: targeting edits apply only to users who have no assignment yet (new users, or after a manual detach). Manual pins behave the same on every project. Contact support if you want your project migrated.
</Note>

Two changes apply **without** any re-evaluation:

* **Payload edits are live.** The payload is read fresh on every request, so attached users see new values on their next fetch regardless of re-evaluation.
* **Archiving or deleting a configuration** releases everyone attached to it immediately.

## Changing experiment traffic mid-flight

Experiment assignments are persisted per user — **including rejections** — which makes experiment traffic changes asymmetric with remote-config rollouts:

* **Raising the traffic allocation** admits only users the experiment has never evaluated. A user previously rejected by the traffic gate was excluded persistently and is **not** re-admitted — the monotonic-ramp property of remote-config rollouts does not extend to experiments.
* **Lowering the traffic allocation** evicts no one: users already assigned keep their group; the new percent gates only first-time evaluations.
* **Changing the group split** (e.g. 50/50 → 70/30) never migrates existing assignments — group membership is drawn at admission and stored. The new weights apply to newly admitted users only.
* **Finishing the experiment** is the only change that releases everyone at once.

## User properties in targeting

User properties set via the SDK participate in targeting rules (`user_properties` conditions) and in audience definitions:

* Properties are batched on the device with a \~5 second debounce, then sent in one request. Backgrounding the app flushes them immediately.
* As covered above, the SDK flushes pending properties **before** every remote config fetch and waits for that flush — the "property race" does not exist on the config path: what you set is what the evaluation sees.
* If a flush fails, the SDK retries with backoff; a cached config served from memory still triggers the flush, so properties are not swallowed by a cache hit.

## Identify and assignments

What happens on `identify(...)` depends on whether the user id changes:

* **First login (identity created for the current user):** the id stays the same. On iOS 6.14.0+, Android 9.7.0+, React Native 10.11.0+, Flutter 11.10.0+, Cordova 7.9.0+, Capacitor 1.8.0+, and Unity 9.9.0+, `identify` drops the config cache **even when the id doesn't change** — the next fetch evaluates fresh against that user's stored assignments and properties. On older SDK versions nothing is re-evaluated by itself: call `invalidateRemoteConfigsCache()` or simply wait for the next cold start.
* **Returning user / second device (identity already exists):** the SDK switches to the existing user id, drops the config cache, and the next fetch evaluates fresh — against the assignments and properties of *that* user. Anonymous-user assignments do not transfer to the identified user.
* **Logout:** the SDK reverts to the original anonymous user of this device (the one active before the first `identify` — no new user is created); the cache is dropped.
* **Repeat `identify` with the already-attached identity:** a no-op on the SDK side — no network call, no cache drop. To force a re-evaluation in that state, call `invalidateRemoteConfigsCache()`.

## Summary

| Question                                                  | Answer                                                                                                                  |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Which config wins among several?                          | Experiment group first; otherwise the first priority-ordered configuration whose rules + rollout match                  |
| Is the rollout random per request?                        | No — deterministic per user; edits never reshuffle membership                                                           |
| Does a user keep their config?                            | Yes — assignments are persisted; payload edits still apply live                                                         |
| When are users re-evaluated?                              | On their next request after a targeting/rules/version/priority/audience change (per-project rollout; manual pins never) |
| What makes assignments immune to re-evaluation?           | Freezing the configuration — new recipients are stored as manual pins; disabling freeze keeps existing pins             |
| Does raising experiment traffic re-admit rejected users?  | No — experiment rejections are persisted; only never-evaluated users are admitted                                       |
| Do user properties apply to the very next config request? | Yes — the SDK force-flushes them before every fetch                                                                     |
| What detaches everyone at once?                           | Archiving or deleting the configuration; finishing an experiment releases its users                                     |
