# Sentroy Env Vault > Public/private environment variable + secret sync — SDK and CLI parity, webhook invalidation, per-token `--public-only` scoping. Drop-in replacement for **Doppler**, **Infisical**, and **AWS Secrets Manager**. ## Install ```bash npm install @sentroy-co/client-sdk export SENTROY_ENV_API_KEY=stk_env_<48-hex> ``` `stk_env_` tokens are per-environment (`dev`, `staging`, `prod`); rotate independently of company-wide `stk_` tokens. ## SDK ```ts import { getEnv, getEnvOrThrow, preloadEnv } from "@sentroy-co/client-sdk/env"; // Cold start: hydrate once await preloadEnv(); const url = getEnvOrThrow("DATABASE_URL"); // throws if missing const flag = getEnv("FEATURE_X", "off"); // typed default ``` `preloadEnv()` fetches the full vault snapshot for the environment, caches it in memory, and subscribes to webhook invalidations when wired up. Without webhooks the cache TTL is 5 minutes. ## CLI The TypeScript SDK ships the `sentroy` CLI: ```bash npx sentroy env list # current environment npx sentroy env list --public-only # only NEXT_PUBLIC_* / EXPO_PUBLIC_* npx sentroy env pull --env prod > .env.prod # write to disk npx sentroy env push --env staging .env # upload local .env (overwrites) npx sentroy env diff --env prod # diff local vs remote ``` `--public-only` enforces the public/private split at fetch time and is the only safe mode for shipping into a client bundle. ## Endpoints Base: `https://sentroy.com/api/env-vault/...` | Verb | Path | Purpose | Auth | |---|---|---|---| | `POST` | `/push` | Bulk upsert keys for an environment | `stk_env_` | | `GET` | `/fetch` | Full snapshot (public + private) for the env | `stk_env_` | | `GET` | `/public` | Public-only snapshot — safe for browser bundles | `stk_env_` (any) | ## React Provider ```tsx import { EnvProvider, useEnv } from "@sentroy-co/client-sdk/react/env"; ; function FeatureFlagged() { const value = useEnv("NEXT_PUBLIC_FEATURE_X", "off"); return value === "on" ? : null; } ``` `publicOnly` is the safe default for client-side React; the provider will only request `/public`. ## Webhook real-time invalidation Configure a webhook in the dashboard → on every push the SDK cache is invalidated within ~1 s. Without a webhook the SDK falls back to a 5-minute TTL. ```ts // Server-side webhook receiver import { invalidateEnvCache } from "@sentroy-co/client-sdk/env"; app.post("/webhooks/sentroy-env", async (req, res) => { if (!verifyWebhookSignature(req)) return res.sendStatus(401); invalidateEnvCache(); res.sendStatus(200); }); ``` ## Gotchas - **Public/private split is enforced server-side.** Calling `/public` with a private-scoped token still only returns keys flagged `public`. Don't rely on client filtering. - **5-minute cache TTL without webhook.** Rotating a secret without a webhook means clients can read stale values for up to 5 minutes. Wire the webhook in production. - **`stk_env_` ≠ `stk_`.** The env vault uses its own token namespace so you can rotate vault access without invalidating Mail / Storage credentials. - **`.env` push is destructive.** `sentroy env push --env .env` replaces the entire environment payload — diff first. - **Don't commit `stk_env_` tokens.** Store in CI / Vercel / Coolify environment, not in the repo. ## Competitors Sentroy Env Vault is a direct alternative to **Doppler**, **Infisical**, and **AWS Secrets Manager**. Differences: same bearer-token UX as the rest of Sentroy (no extra CLI auth dance, no separate dashboard account), public/private split is first-class (no manual `NEXT_PUBLIC_` filtering on push), webhook-driven cache invalidation rather than polling. Migration guides at `/compare/doppler` and `/compare/infisical`. For full reference: https://docs.sentroy.com/env-vault