Flag evaluation is entirely local — it runs inside the SDK against a cached snapshot. There is no per-evaluation network call. The network is only used to periodically refresh the snapshot in the background.
Evaluation algorithm
When you call useFlag, useFlagValue, client.IsEnabled, or any other evaluation method, the SDK runs the following steps in order:
1. Flag not in snapshot?
→ Return caller default (reason: ERROR, code: FLAG_NOT_FOUND)
2. Wrong type requested?
→ Return caller default (reason: ERROR, code: TYPE_MISMATCH)
3. Flag disabled?
→ Return flag default (reason: STATIC)
4. Walk targeting rules top → bottom:
a. All conditions match?
→ Return rule's serve value (reason: TARGETING_MATCH, ruleId: <id>)
5. Flag-level rollout configured?
→ Deterministically bucket user
→ Boolean flag: return true/false (reason: ROLLOUT)
→ Multi-value: return split value (reason: SPLIT)
6. No match
→ Return flag default (reason: DEFAULT)
Steps 1–3 are guard checks. Steps 4–6 are the evaluation proper.
Reason codes
Every evaluation produces a reason code. Access it via the *Detail / useFlagVariant APIs.
| Reason | When it occurs |
|---|
STATIC | Flag is disabled — the flag’s configured default value is returned |
DEFAULT | Flag is enabled, no rule matched, no rollout is configured |
TARGETING_MATCH | A targeting rule matched; the rule’s serve value is returned |
SPLIT | User was bucketed into a multi-value traffic split |
ROLLOUT | User was bucketed into a boolean rollout |
ERROR | Evaluation could not complete — see errorCode for the specific failure |
Error codes
Error codes appear on results with reason ERROR.
| Code | Cause | SDK behaviour |
|---|
FLAG_NOT_FOUND | Flag key not present in the snapshot (flag disabled, archived, or wrong environment) | Returns the caller-supplied default |
TYPE_MISMATCH | The flag exists but its type doesn’t match the method called (e.g. GetString on a number flag) | Returns the caller-supplied default |
MISSING_TARGETING_KEY | Rollout or traffic split requires a bucketing key, but the evaluation context contains none | Returns the flag’s default value |
PROVIDER_NOT_READY | SDK has not yet loaded its first snapshot | Returns the caller-supplied default |
FLAG_NOT_FOUND is the normal outcome when a flag is disabled. Because disabled flags are excluded from the snapshot, the SDK cannot distinguish between “flag disabled” and “flag doesn’t exist” — both appear as FLAG_NOT_FOUND. For boolean flags this is always transparent, since both FLAG_NOT_FOUND and a disabled flag return false.
Deterministic bucketing
Rollout and traffic splits use a FNV-1a (32-bit) hash to assign each user a bucket position between 0 and 100,000. The hash input is:
{flagKey} + {envKey} + {targetingKey} + {salt}
The targetingKey is resolved from the evaluation context using the first available value in this priority order:
customerId → agentId → businessId → businessBranchId → walletId → customerRef → omnipayId
Because the hash is deterministic, the same user always lands in the same bucket for the same flag, across all SDK platforms, restarts, and devices. A user who receives true for a flag at 10% rollout will continue to receive true as that rollout expands to 20%, 50%, and 100%.
If none of the bucketing keys are present in the context, rollout-based evaluation returns MISSING_TARGETING_KEY and falls back to the flag’s default value.
Snapshot caching
| Platform | Storage | Warm start |
|---|
| React | In-memory | No — isLoading is true until the first fetch completes |
| React Native | AsyncStorage | Yes — flags available immediately from the persisted cache |
| .NET | In-memory | No — the hosted service loads the snapshot at startup, before requests arrive |
All SDKs refresh the snapshot on a polling interval configured server-side (default: 2 minutes). Refreshes use HTTP ETags — if the snapshot hasn’t changed, the CDN returns 304 Not Modified and no data is transferred. React Native additionally refreshes on foreground resume.
Stale snapshot behaviour
If the SDK cannot reach the CDN during a refresh attempt (network error, timeout, CDN outage), it continues using the last successfully fetched snapshot. Evaluation is uninterrupted. The client status reflects the failure:
isFetching returns to false
error is set to the fetch error
origin remains 'CACHE' or 'SERVER' (the source of the snapshot currently in use)
The SDK will retry on the next polling interval. As long as a snapshot has been loaded at least once, there is no degradation in flag evaluation capability during outages.