Skip to main content
Every user who interacts with your agent becomes a row in the audience a directory FirstFlow assembles from the identity you send. Segments are named subsets of that directory, and an experience can target one so that only matching users are eligible to see it. Targeting is evaluated server-side: when a user qualifies for an experience, FirstFlow checks the experience’s audience setting against that user’s segment membership before composing and pushing a widget. The browser cannot override this segment membership is computed from data the backend already holds.

How it works

The audience is not something you upload. FirstFlow writes a member row the first time it sees a user, then keeps it current from two sources: the browser user prop on FirstflowProvider, and the server identify() helper. The id you send becomes the row’s stable externalUserId; the name, email, and traits become the fields you can filter and segment on. Send more, target more precisely. A segment is a saved definition that resolves to a set of those rows. FirstFlow splits segments into two kinds by how membership is determined:
  • A dynamic segment has one or more filter rules (for example, sessions greater than 5). FirstFlow evaluates the rules against the directory and maintains the matching set for you.
  • A manual segment has no rules. Membership is assigned explicitly by the assign_segment flow action, or by the dashboard’s “Add to segment” affordance on the audience list.
The crucial mechanism is that membership is precomputed, not evaluated at delivery time. FirstFlow stores each user’s segment IDs in an array on their audience row (agent_audience.segments[]). SegmentMembershipService recomputes that array whenever the user’s data changes (a new session, lastSeenAt, traits) and whenever a segment’s rules change. Eligibility at delivery is then a fast array-contains check no rules are re-run on the hot path. This is why targeting is reliable but eventually consistent: edit a dynamic segment’s rules and the affected members are re-synced in the background, not the instant a widget would fire.

Where audience fits in eligibility

Audience is one gate in a server-side sequence. When an observed message arrives, ConversationRouterService finds experiences whose trigger matches, then for each candidate checks audience, then frequency, then the optional LLM classifier. Only an experience that clears every gate is composed (Anthropic claude-sonnet-4-6) and pushed over Socket.IO.

The audience gate

An experience stores its targeting in settings.audience, a small object with two fields:
When type is all, every user passes the gate. When type is segment, the backend loads the user’s audience row and checks whether segment_id is present in their precomputed segments[] array. Present means eligible; absent means the experience is skipped for that user. Two fallbacks make the gate fail open rather than silently hide everything:
  • Unknown user. If the user has no audience row yet common on a first visit before identity has propagated the gate passes conservatively. A brand-new user is not excluded just because their row hasn’t been written.
  • Lookup error. If the membership lookup throws, the gate passes conservatively and logs a warning, so a transient database issue never blanket- suppresses experiences.
A deleted segment is handled at the data layer: deleting a segment purges its ID from every member’s segments[]. An experience still pointing at that ID matches nobody by membership, so targeting effectively reverts to “all users.” Re-point the experience at a live segment to restore narrow targeting.

Defining segments

A segment’s rules are an array of { field, operator, value } objects. Dynamic membership is computed by translating those rules into a query over the audience directory, so rules operate on the directory’s own columns: Rules referencing any other field are skipped during membership evaluation, so a rule on an arbitrary trait will not narrow a dynamic segment keep dynamic rules to the fields above, and use a manual segment (assigned in a flow) for richer, trait-driven grouping. Create a segment through the typed API client; FirstFlow recomputes its user count and schedules the background membership sync:
To target an experience at it, set the experience’s audience to that segment in the dashboard sidebar, or write the setting directly:

Keep the directory rich

Targeting is only as good as the traits you send, and segments can only filter on data that exists. Set identity early on login, and again whenever a trait changes so a user’s row is populated before any experience tries to target them.
The browser traits and server identify() traits both land on the same member row, keyed by the id / userId you send. Use a stable id (your own user primary key), not an email or session value, so updates accumulate on one row instead of forking into duplicates.

Specific cases

Audience is one of several gates. Confirm the experience is active, its trigger matches the message, the frequency cap isn’t exhausted, and if configured the classifier allows. Audience only decides segment eligibility, not whether anything fires.
Membership is precomputed and synced in the background after a rules change. The cached userCount is refreshed immediately from the new rules; the per-user segments[] arrays are reconciled asynchronously. Targeting catches up shortly after.
Dynamic rules evaluate only over name, email, sessions, lastActivity, and createdAt. A rule on any other trait is skipped. For trait-driven grouping, use a manual segment and assign members with the assign_segment flow action or the dashboard’s “Add to segment” affordance.
Deleting a segment removes its ID from every member’s segments[]. An experience still pointing at the dead ID matches nobody by membership, so it effectively targets all users. Re-point it at a live segment to restore the narrower audience.

Next

Segments share their condition vocabulary with triggers and audience, and the data they filter comes from the identity you send from the browser and the server SDK. To see how a qualifying user turns into a delivered widget, read experiences.