Dashboard

Proxy Toggld through your own domain

Send every SDK request through a proxy on your own domain, with endpoint as a base URL. Paths, headers, WebSockets, and example proxies.

Some apps must send all of their traffic to their own domains, for example because their customers allow only those domains. Then put a proxy on your domain in front of the Toggld API, and point the SDKs at it.

Set endpoint

Both SDKs take endpoint, the base URL of the Toggld API (default https://api.toggld.io). Set it to the URL of your proxy. It can have a path prefix, with or without a trailing slash. The SDKs keep the prefix and add their paths after it.

Server: @toggld/js
import { ToggldClient } from "@toggld/js";

const flags = await ToggldClient.create({
  sdkKey: process.env.TOGGLD_SDK_KEY!,
  endpoint: "https://flags.example.com/toggld",
});
Browser: @toggld/browser
import { ToggldClient } from "@toggld/browser";

const flags = await ToggldClient.create({
  publicToken: "ffp_YOUR_PUBLIC_TOKEN",
  endpoint: "https://flags.example.com/toggld",
  context: { targetingKey: "user-123" },
});

With this endpoint, the browser SDK sends POST https://flags.example.com/toggld/ofrep/v1/evaluate/flags and opens wss://flags.example.com/toggld/eval/v1/stream. The proxy removes /toggld and sends the rest to https://api.toggld.io. With @toggld/react, set endpoint on the ToggldClient that you pass to the provider. The serverless client and the toggld pull CLI (--api or TOGGLD_API_URL) take the same base URL.

Paths

Your proxy must pass these paths and methods. Each path comes after your prefix.

ClientMethod and pathUsed for
@toggld/js, sync mode (default)GET /sync/v1/flagsFlag snapshot, with If-None-Match
GET /sync/v1/stream (WebSocket)Live updates
POST /v1/telemetryEvaluation counts for Analytics
@toggld/js, remote modePOST /ofrep/v1/evaluate/flags/{key} and POST /ofrep/v1/evaluate/flagsOne flag, and every flag
@toggld/js/serverlessPOST /ofrep/v1/evaluate/flagsOne bulk request per incoming request
toggld pullGET /sync/v1/flagsDownload a flag file
@toggld/browser, @toggld/reactPOST /ofrep/v1/evaluate/flagsValues over HTTP
GET /eval/v1/stream (WebSocket)First values and live updates

Headers

Pass these headers through unchanged:

DirectionHeaders
RequestAuthorization (the server SDK's secret key, and the browser SDK's public token on HTTP), Content-Type, If-None-Match
Request, WebSocketUpgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol
ResponseContent-Type, ETag, Retry-After, Cache-Control
Response, WebSocketSec-WebSocket-Protocol
  • The WebSocket credential is in Sec-WebSocket-Protocol. A browser WebSocket cannot send an Authorization header, so both SDKs send the key or token as a subprotocol entry, toggld.bearer.<token>, next to toggld.sync.v1 (server SDK) or toggld.eval.v1 (browser SDK). The proxy must pass the header to Toggld, and pass back the subprotocol that Toggld selects in its answer. Do not log this header.
  • Do not forward your own cookies. The SDKs do not need them.
  • Do not cache responses in the proxy. Toggld sets Cache-Control itself.

Browsers and CORS

A page on another origin than your proxy makes cross-origin requests. Then your proxy must answer CORS preflight (OPTIONS) requests for the OFREP paths: allow your page origin, the POST method, and the Authorization and Content-Type headers, and expose Retry-After. A proxy on the same origin as the page needs no CORS. WebSockets do not use CORS.

WebSockets

Both SDKs work through a proxy that cannot pass WebSockets, with slower updates:

  • @toggld/js in sync mode keeps polling GET /sync/v1/flags, every 30 seconds by default (sync.pollIntervalMs), and retries the stream with a backoff of up to about 30 seconds. Changes arrive with the next poll. To stop the stream attempts, set sync: { transport: "poll" }.
  • @toggld/browser waits up to 1.5 seconds for the stream, then asks over HTTP. It remembers for the browser session that the stream did not answer, and later pages use HTTP only. Without the stream there are no live updates: turn on pollIntervalMs to pick up changes on long-lived pages, or set stream: false to skip the wait. See Live updates.
  • Remote mode and the serverless client use HTTP only.

A WebSocket stays open for a long time. Both SDKs send a heartbeat every 30 seconds, so an idle timeout of at least 60 seconds is enough.

Rate limits through a proxy

The Toggld API counts its per-address rate limits by the address it receives the request from, which is now your proxy. All traffic through the proxy shares those limits: 1,200 requests per minute for API requests from one address, and 120 evaluation requests per minute per environment for public tokens. Evaluation and sync requests with a secret key are exempt from the per-address limit; they share 30,000 requests per minute per environment. Browser traffic through one proxy address reaches the public token limit quickly. Plan for this before you send browser traffic through a proxy.

Example proxies

These are examples, not part of Toggld. Adapt them to your setup.

Cloudflare Worker

Example: proxy Worker
const UPSTREAM = "https://api.toggld.io";
const PREFIX = "/toggld";

export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    if (!url.pathname.startsWith(`${PREFIX}/`)) return new Response("Not found", { status: 404 });
    const upstream = new Request(new URL(url.pathname.slice(PREFIX.length) + url.search, UPSTREAM), request);
    upstream.headers.delete("cookie");
    // fetch also passes WebSocket upgrades: the 101 response carries the socket.
    return fetch(upstream);
  },
} satisfies ExportedHandler;

Route it on your domain, for example flags.example.com/toggld/*.

nginx

Example: nginx
# In the http block:
map $http_upgrade $connection_upgrade {
  default upgrade;
  ""      close;
}

# In the server block of your domain:
location /toggld/ {
  proxy_pass https://api.toggld.io/;        # the trailing slash removes /toggld
  proxy_http_version 1.1;
  proxy_set_header Host api.toggld.io;
  proxy_ssl_server_name on;                 # send SNI for api.toggld.io
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection $connection_upgrade;
  proxy_set_header Cookie "";
  proxy_read_timeout 120s;                  # WebSocket heartbeats come every 30 seconds
}

On this page