Skip to main content

WebSocket sync and definitions revision

Client-side SDKs connect to the definitions worker WebSocket channel to learn when flag data changed, without polling on every reconnect. HTTP fetches use the same definitions revision (etag) for conditional requests.

Overview​

sequenceDiagram
participant SDK
participant Worker

SDK->>Worker: WS connect /{appKey}/ws?rev=cachedRevision&sdk=...&sdkVersion=...
Worker-->>SDK: sync unchanged=true
Note over SDK: No HTTP fetch

Worker-->>SDK: flags-updated etag=newRevision
SDK->>Worker: GET evaluated-signed If-None-Match:cachedRevision
Worker-->>SDK: 200 + ETag + X-Definitions-Revision

On connect the worker sends a lightweight sync message. SDKs compare the revision and skip HTTP when nothing changed. When definitions or the signing key change, the worker pushes an update message and the SDK fetches only if needed.

Definitions revision (single etag)​

There is one revision per app/environment derived from the replicated definition document on the worker — not from per-user evaluated output.

  • Same value on WebSocket (sync, flags-updated) and HTTP (ETag, X-Definitions-Revision)
  • SDKs cache one revision alongside flags (localStorage, secure storage, etc.)
  • Sent as If-None-Match on evaluated-signed, evaluated-variants-signed, and related GET endpoints
  • Bumps when definitions, segment data embedded in definitions, or allowedOrigins change

Persist the revision in your cache provider so cold starts can send ?rev= on WebSocket connect.

WebSocket URL​

wss://definitions.toggly.io/{appKey}/ws?rev={cachedRevision}&sdk={sdkId}&sdkVersion={semver}

Environment-scoped connections also support ?rev= and SDK identity query params:

wss://definitions.toggly.io/{appKey}/{environment}/ws?rev={cachedRevision}&sdk={sdkId}&sdkVersion={semver}

SDKs always append sdk and sdkVersion on WebSocket connect (browsers cannot set custom headers on the upgrade request). When no cached revision exists, omit rev but keep sdk and sdkVersion.

SDK identity on HTTP​

Server-side SDKs (Node, .NET, Go, mobile native, etc.) send:

User-Agent: toggly-{sdkId}/{semver}

Browser, React Native, and Flutter web clients send custom headers instead (User-Agent is forbidden in browser fetch()):

X-Toggly-Sdk: {sdkId}
X-Toggly-Sdk-Version: {semver}

Examples:

  • Node: User-Agent: toggly-node/0.2.1
  • React (browser): X-Toggly-Sdk: react, X-Toggly-Sdk-Version: 1.5.1

The definitions worker parses all three encodings (custom headers, User-Agent, WebSocket query params) and logs structured SDK identity for future version-based decisions. Custom headers require CORS preflight; allowed origins must include your app origin (existing allowlist).

When HTTP definitions are proxied through your backend (customDefinitionsUrl), SDKs still connect WebSocket to definitions.toggly.io — WebSocket is the change-notification channel; HTTP may be proxied separately.

Server messages​

sync (on every connect)​

{ "type": "sync", "etag": "abc123", "lastUpdated": 1710000000000 }

When the client's rev query param matches the current revision:

{ "type": "sync", "etag": "abc123", "lastUpdated": 1710000000000, "unchanged": true }

flags-updated (definitions changed)​

{ "type": "flags-updated", "etag": "def456", "lastUpdated": 1710000000000 }

signing-key-updated (global signing key rotated)​

{ "type": "signing-key-updated", "kid": "key-id", "lastUpdated": 1710000000000 }

Clients refetch JWKS (/.well-known/jwks) and signed definitions when this arrives.

Client fetch rules​

EventAction
sync + unchanged: trueStore revision, no fetch
sync without unchanged, no cached revisionFetch
sync without unchanged, revision differsFetch
sync without unchanged, revision matchesStore revision, no fetch
flags-updated, revision differsFetch (debounced)
flags-updated, revision matchesNo fetch
signing-key-updatedFetch JWKS + definitions (debounced)

SDKs debounce rapid updates (typically 300ms) and use exponential backoff on WebSocket reconnect (5s → 60s cap).

HTTP conditional requests​

All definition GET responses include:

ETag: abc123
X-Definitions-Revision: abc123

Send the cached revision on subsequent requests:

If-None-Match: abc123

A 304 Not Modified response means flags are unchanged; SDKs keep the last-known-good map.

See Evaluated-signed for endpoint URLs and response shape.

Legacy connect push (migration)​

During rollout, the worker may still send legacy full-payload messages on connect (definitions or evaluated) when WS_LEGACY_CONNECT_PUSH is enabled (default). Modern SDKs ignore these and rely on sync + conditional HTTP.

Operators can disable legacy pushes in the Cloudflare worker environment after validating metrics (DO compute, request count, 304 ratio).