Orishare
Loyalty guides

Dynamic segments

Segments defined by conditions over attributes, balances, tiers, account age and event activity, recomputed on a schedule and usable as any audience.

A segment is a named group of customers. A static segment holds the members you add. A dynamic segment holds every customer who matches a definition: "gold members who have not ordered in 90 days", "customers with more than 500 points", "new accounts in Texas".

Orishare recomputes the members of every dynamic segment about once an hour and stores them. Campaign audiences, rule conditions over customer.segments, leaderboards and schedules read the stored members, so a dynamic segment works anywhere a static one does.

Create one

lapsed-gold.json
{
  "name": "lapsed_gold",
  "kind": "dynamic",
  "definition": {
    "all": [
      { "path": "customer.tiers.status", "op": "eq", "value": "gold" },
      { "path": "customer.activity.order.completed.count_365d", "op": "gte", "value": 1 },
      { "path": "customer.activity.order.completed.days_since_last", "op": "gte", "value": 90 }
    ]
  }
}

Send it with POST /v1/segments and an Idempotency-Key. The definition is the same condition tree rules use: a comparison { path, op, value }, or all, any and not around other conditions. An invalid definition is refused with 422 rule_invalid, and param names the offending node.

What a definition can read

PathValueOperators
customer.attributes.<key>The attribute, any JSON type. Nested keys with dots.All rule operators
customer.balances.<currency>Number: available balance in that currency codeeq neq gt gte lt lte between in not_in exists
customer.lifetime_earned.<currency>Number: everything ever earned in that currencysame
customer.tiers.<program>String: the level name in that tier program (program id or name)eq neq in not_in exists
customer.created_days_agoNumber: whole days since the customer was creatednumber operators
customer.activity.<event>.days_since_lastNumber: whole days since the last <event>; unknown if none in 365 daysnumber operators
customer.activity.<event>.count_30dNumber: <event> events in the last 30 daysnumber operators
customer.activity.<event>.count_90dNumber: <event> events in the last 90 daysnumber operators
customer.activity.<event>.count_365dNumber: <event> events in the last 365 daysnumber operators
customer.activity.<event>.sum_<property>_365dNumber: sum of a numeric event property over the last 365 daysnumber operators

<event> is the event name as you send it, dots included: customer.activity.order.completed.count_90d counts order.completed events. <property> is a top-level numeric property of that event, for example sum_total_cents_365d for spend. Activity never looks back further than 365 days.

Rules that keep definitions predictable:

  • A value that is unknown never matches, and neither does its negation: a currency or tier program that does not exist, a missing attribute, days_since_last for a customer with no such event. Counts and sums are never unknown; they are 0.
  • A customer without a tier row is at the program's first level.
  • not may wrap one comparison of a balance, tier, age or activity path, or any group of attribute conditions. It may not wrap a group that contains those paths, or exists on them. For "never did X", write count_365d eq 0.
  • A definition has at most 50 comparisons and reads the activity of at most 8 different events.
  • Attribute timestamps are compared as times only when they are full RFC 3339 values with an offset, such as 2026-03-01T12:00:00Z.
  • Merged customers count once, under the customer they were merged into, with the balances and activity of both.

Members, freshness and refresh

GET /v1/segments/{id} reports three fields for every segment:

FieldMeaning
membersThe number of stored members. null for a dynamic segment that was never computed.
computed_atWhen the members of a dynamic segment were last computed. null for static segments.
staletrue when the stored members do not reflect the current definition yet, or the last computation is over two hours old. Always false for static segments.

A new or edited dynamic segment is stale until its next computation, which the schedule picks up within minutes. To compute it right away, call POST /v1/segments/{id}/refresh with an Idempotency-Key. It recomputes the members and returns the segment.

GET /v1/segments/{id}/members lists the stored members of either kind. Members of a dynamic segment cannot be added or removed by hand; change the definition instead.

GET /v1/segments/{id}/eligibility/{customer_id} answers for one customer against the current definition, so it can be ahead of the stored members between two computations.

Use it as an audience

Name the segment wherever an audience is accepted, exactly like a static one:

campaign.json
{ "name": "Win back gold", "status": "active", "audience": { "segments": ["lapsed_gold"] } }

A customer starts and stops matching at the next computation, not at the moment their balance or activity changes. Segments defined over attributes only are also checked live at rule time: a customer who starts matching after an attribute change is in the audience at once, and one who stops matching leaves at the next computation.

On this page