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

# OpenFeature Web Provider - Targeting

> Per-user flag evaluation through the Nexus OpenFeature Web provider: when it fetches, what it sends, and what each reason means.

A boolean evaluation whose evaluation context carries a `targetingKey` is answered for that user: AB tests, cohort rules and percentage rollouts all apply.

```ts theme={null}
await OpenFeature.setContext({ targetingKey: user.id, plan: user.plan });

const client = OpenFeature.getClient();
const inBeta = client.getBooleanValue('beta.enabled', false);
// -> the answer for this user, reason: TARGETING_MATCH
```

## When the request happens

`resolveBooleanEvaluation` is **synchronous** in the OpenFeature Web SDK, so there is no point at which an evaluation could wait for a network call. The provider asks the service when something changes, and answers every evaluation from what that produced.

| Trigger                                                                     | What happens                                                                                                        |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `initialize`, with a context that already carries a targeting key           | One request before the provider reports ready.                                                                      |
| `setContext` with a different targeting key or a different string attribute | One request; `setContext` returns when it has settled.                                                              |
| `setContext` that changes neither                                           | No request. A context object rebuilt with the same contents is a re-render, not a new question.                     |
| The configuration changes, over the event stream or a poll                  | The same answers are re-asked, so a flag change does not leave a targeted evaluation serving the pre-change answer. |
| A stream reconnect, or a 304                                                | No request. Nothing about the configuration moved.                                                                  |
| Every individual evaluation                                                 | No request. It is a local read.                                                                                     |

<Note>
  A targeting-key change is asynchronous - `await OpenFeature.setContext(...)`. An evaluation issued before that resolves is answered from the snapshot with `STALE`, never from the previous user's values.
</Note>

One request covers **every flag in the snapshot**, chunked at the service's 200-key limit, so a page reading twenty flags costs one call rather than twenty. Concurrent triggers for the same context produce one request, not several.

## What is sent

```json theme={null}
POST /v1/flags/evaluate-ab
{
  "keys": ["beta.enabled", "banner.enabled"],
  "user_id": "<targetingKey>",
  "attributes": { "plan": "pro" }
}
```

**Only string context fields become attributes.** The service's cohort matcher compares strings with `eq` / `neq` / `in`, so a non-string field cannot participate in a rule. One is dropped and reported through the provider's logger rather than stringified - a coerced value that then matches nothing is harder to diagnose than an absent one.

```ts theme={null}
await OpenFeature.setContext({
  targetingKey: user.id,
  plan: 'pro',      // sent
  age: 30,          // dropped, and logged at warn level
  beta: true,       // dropped
  tags: ['a', 'b'], // dropped
});
```

Numeric and boolean targeting is a separate service capability - the cohort matcher needs typed operators for it - not a coercion the provider can supply.

`targetingKey` travels as `user_id` and never also as an attribute, so a cohort rule cannot be written against an attribute the provider only sends by accident.

## What each reason means

| `reason`                     | Meaning                                                                                                                                                                                                                                                                                 |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TARGETING_MATCH`            | Answered for the identity in the context. This also covers a percentage rollout: the endpoint reports the resolved value rather than which rule produced it, so `SPLIT` cannot be distinguished.                                                                                        |
| `STATIC`                     | From the synced snapshot. Either the context carries no targeting key - a caller that passes no identity is completely unaffected by targeting - or the project does not have the AB Testing add-on, in which case the snapshot *is* authoritative and there is nothing stale about it. |
| `STALE`                      | From the snapshot while an identity is in force: the targeted request failed, or the flag entered the snapshot after the last one. The value is real, just not targeted.                                                                                                                |
| `DEFAULT` + `FLAG_NOT_FOUND` | The service does not define this flag.                                                                                                                                                                                                                                                  |

## When the service is unreachable

A transport failure resolves from the snapshot with `STALE` and **no error message**. Reporting an error there would hand back the caller's default - `false` for a flag that is genuinely on - because of one failed request, which is the worst available outcome.

Previously fetched targeted values are kept rather than cleared: clearing them would turn a transient error into a visible flag flip.

## When the AB Testing add-on is not active

The service answers HTTP 403, and the provider resolves from the snapshot with `STATIC`, then stops asking for five minutes (`addonSuppressionMs`).

It is a **throttle, not a latch**: the window expires on its own, any success clears it immediately, and a project that buys the add-on starts getting targeted results within the window with nothing to restart.

## Seeing the diagnostics

A dropped context field is reported at `warn` level, which OpenFeature's `DefaultLogger` (the default) already writes to the console. A failed targeted fetch and an inactive add-on are reported at `debug` level, which `DefaultLogger` discards - pass a logger that keeps `debug` to see them:

```ts theme={null}
new NexusProvider(
  { baseUrl: 'https://blue-ocean-a5rx7.westyx.dev', apiKey: 'wxp_...' },
  { logger: console },
);
```

The resolve methods receive a logger from the Web SDK, but the calls that have something to report - the fetches, which happen on a context change - are the ones with no logger in scope, which is why the option exists.

## What is not targeted

String, number and object evaluations map to **configuration values**, and targeted evaluation is a flag concept. There is no such thing as a targeted config value, so those resolutions are unaffected by the evaluation context.
