Evaluated-signed responses
Toggly client SDKs fetch pre-evaluated boolean flags from the worker at:
https://definitions.toggly.io/evaluated-signed/{appKey}/{environment}
Personalization is passed as query parameters on that URL (see Evaluation context below).
What you get
- User-side filters (targeting, percentage, user claims, browser/country/device/OS, time window) are evaluated on the worker
- Features with Context Property filters may return an EntityGate object instead of a plain boolean when user filters pass — see Entity & page context
- Responses can be signed (verify when your SDK supports it, e.g. Flutter)
- The client caches the remote map in memory (and optionally persistent storage)
- Reads resolve
boolean | EntityGatelocally when you pass entity context; optional post-filter gates AND device-local prerequisites at read time
See the Feature filters reference for parameter details and context requirements per filter type.
Evaluation context
Client SDKs pass targeting data with setContext() (or equivalent), which the worker reads from the query string:
| Context | Query parameter | Used by |
|---|---|---|
| Identity | ?u= (evaluated) or ?userId= (variants) | Percentage, Targeting |
| Groups | ?g= (repeatable) | Targeting group rules |
| Claims | ?claim.{type}={value} (max 20 types) | User Claims filter |
Example:
await toggly.setContext({
identity: 'user-123',
groups: ['beta', 'admins'],
claims: { role: 'admin', plan: 'premium' },
});
// GET .../evaluated-signed/{appKey}/{env}?u=user-123&g=beta&g=admins&claim.role=admin&claim.plan=premium
Browser-based filters (Country, Browser Family, Device Type, etc.) use request headers automatically (User-Agent, Accept-Language, CF-IPCountry) — no SDK fields required.
When identity, groups, or claims are present, responses use Cache-Control: private so shared caches do not serve one user's evaluation to another.
Mixed definitions payload
Evaluated-signed defs is a map of feature key → boolean | EntityGate:
| Value | Client behavior |
|---|---|
true / false | Final user-side result (no entity rules, or user filters failed) |
{ requirement, rules } | User filters passed; evaluate rules locally against entity context |
import { isEntityGate, resolveEvaluatedDefinition } from '@ops-ai/toggly-hooks-types';
const raw = flags['OrderBadge'];
if (isEntityGate(raw)) {
const on = resolveEvaluatedDefinition(raw, entityContext); // fails closed without context
}
Do not use flags[key] === true or truthy checks — gate objects are objects, not booleans. Old SDKs that only check === true treat gates as off.
Percentage / rollout is user-only — entity rules are never percentage-bucketed. Entity evaluation happens per widget/check on the client after fetch.
Trust and security
Evaluated-signed is designed for feature rollout, not as an authorization service.
Client-supplied context is not proof of identity
For client-side apps, u / userId, g, and claim.* query parameters are chosen by the SDK in the browser. A user can edit the URL or devtools network tab and assert any identity or claim. The worker evaluates filters against what it receives — it does not validate JWTs or server sessions on these public endpoints.
| Use case | Safe on client-side? | Recommended approach |
|---|---|---|
| Gradual rollout, A/B tests, beta cohorts | Yes | setContext() + targeting / User Claims filters |
| Admin UI, billing, PII, security-sensitive APIs | No | Backend .NET filters with authenticated HttpContext claims |
| “Only employees see this button” (low risk UX) | Maybe | Accept spoofing risk, or enforce on server when the action runs |
Sensitive data in URLs
Evaluation context is sent on the query string. That can appear in browser history, referrer headers (if you link off-site), and edge access logs.
- Use opaque IDs for
identity(internal user GUID), not email or legal name. - Keep claims to coarse rollout dimensions (
role,plan,beta) — not SSN, payment tokens, or full profile payloads. - Maximum 20 claim types per request; additional types are ignored (alphabetically first types are kept).
Cache-Control: private prevents shared CDN caches from mixing evaluations between users. It does not hide query parameters from logs or the client.
Conditional requests (definitions revision)
Responses include a definitions revision used for efficient updates:
ETag: abc123
X-Definitions-Revision: abc123
SDKs send the cached revision on subsequent requests:
If-None-Match: abc123
When definitions are unchanged, the worker returns 304 Not Modified and the SDK keeps the last-known-good flags.
The revision is the same value used on the WebSocket sync channel (sync, flags-updated). One cached revision per app/environment — not per-user. After a WebSocket sync notification, SDKs refetch with full context rather than applying anonymous evaluated payloads.
What you do not get
- Raw flag definitions or targeting rule DSL on the device
- Server-side security enforcement — always protect sensitive behavior on your backend
Related
- Feature filters reference
- WebSocket sync and definitions revision
- Post-filter gates — device-local master switches