Skip to Content
GuidesRotate everything (no downtime)

Rotate everything (no downtime)

How to swap every secret in your Synq integration — client_secret, BYO OAuth credentials, webhook secrets, API keys, SESSION_SECRET, JWKS signing keys — without dropping a single sign-in, webhook delivery, or background job.

The whole-page idea: for every secret, register the new one before retiring the old one. The window where both are valid is when you flip clients. After clients are flipped, the old secret is revoked.

Rotation cadence we recommend

SecretCadenceWhy
OIDC client_secretevery 90 daysLowers the blast radius of a leak through any vector — CI logs, env-var screenshots, ex-employee laptops.
BYO OAuth client secretsevery 90 daysGoogle, Apple, Microsoft, Discord all support rotation; doing it on a schedule normalizes the workflow.
Webhook signing secretevery 180 daysLower frequency because rotation requires a coordinated change in your webhook receiver.
API keys (user-issued)per-revocationUsers rotate their own; you don’t have to.
SESSION_SECRETevery 365 days or any team-member departureA rotated session secret invalidates all existing sessions — users re-login. Pick a quiet window.
Synq JWKS signing keysSynq rotates these automaticallyYou don’t manage these; keep your JWKS cache TTL ≤ 24 hours.

OIDC client_secret

The dashboard supports a second active secret per App. Use it to roll without downtime.

  1. Dashboard → App → Credentials → “Add secondary secret”. A new secret is generated. Both secrets are now valid for authenticating to the token endpoint.
  2. Update your deployment env var (SYNQ_CLIENT_SECRET) to the new secret. Wait for the deployment to finish rolling.
  3. Verify: sign in to your app, confirm it works. The OIDC callback exchanges the auth code using the new secret.
  4. Dashboard → “Revoke primary secret”. The old secret is immediately rejected by Synq’s token endpoint. Any old deployment pinned to the old secret will fail to exchange new auth codes — but existing sessions remain valid until they expire (the secret is used only at the token endpoint, never for already- issued tokens).

Total downtime: 0. Total window where both secrets are valid: ~5 minutes to an hour, depending on your deployment cadence.

BYO OAuth credentials (Google, Apple, etc.)

Each provider has its own rotation flow. The Synq side is the same for all: register the new credential, run sign-ins through it, then remove the old.

Google OAuth 2.0 client

Google supports two client secrets per OAuth client out of the box.

  1. Google Cloud Console → APIs & Services → Credentials → your OAuth 2.0 Client → “Add secret”. You now have two valid secrets.
  2. Synq dashboard → Brand → Connections → Google → “Replace credentials” → paste new secret. Save.
  3. The first sign-in flow that runs uses the new credential. You can verify by triggering a Google sign-in from an incognito window; if it succeeds, the new credential is wired.
  4. Google Cloud Console → “Delete old secret”. The old secret is immediately invalidated by Google.

Apple Sign-In

Apple is the trickiest because the “client secret” is a JWT minted from a .p8 key. To rotate, you swap the .p8 key:

  1. Apple Developer → Keys → Create a new key → enable Sign in with Apple → assign your existing Service ID → download the .p8 file. Note the Key ID.
  2. Synq dashboard → Brand → Connections → Apple → “Replace key” → upload the new .p8, paste the new Key ID. Save.
  3. Verify with an Apple sign-in from incognito.
  4. Apple Developer → “Disable old key”. Apple deactivates the old key. Existing sessions remain valid because Apple’s JWT is used only at the token endpoint.

Microsoft, Discord, Facebook, X, Telegram

Same shape. Each platform’s developer console supports either multi-secret or “regenerate” with a configurable grace window.

Webhook signing secret

Webhook signing rotation requires a coordinated change because your verifier must accept both old and new signatures during the window.

  1. Dashboard → Webhook → “Add secondary signing secret”. Synq will now sign each delivery with both the primary and secondary secret, in two Synq-Signature headers.
  2. Update your verifier to accept either signature:
    import { verifyWebhookSignature } from '@synqid/js' const headers = request.headers.get('synq-signature')!.split(',') const ok = headers.some((sig) => verifyWebhookSignature({ signature: sig, payload: rawBody, secret: PRIMARY_OR_SECONDARY_SECRET, }) )
  3. Deploy. Confirm webhooks are being verified successfully under both secrets (look at your logs).
  4. Dashboard → “Promote secondary, retire primary”. Synq stops signing with the old secret.
  5. Update your verifier to drop the old secret. Deploy.

The whole rotation takes one deploy cycle. The window where both secrets are valid is bounded by the time between steps 2 and 5.

API keys

Two flavors:

  • Personal API keys (users create their own at /api-keys): the user rotates them when they want. Your app does not rotate these for them.
  • m2m API keys (server-to-server tokens you create for your backend): treat exactly like client_secret — Synq supports two-active rotation. Same flow.

SESSION_SECRET

This one breaks sessions on rotation. iron-session uses SESSION_SECRET to encrypt the cookie; rotating the secret means existing cookies cannot be decrypted.

Plan it for a low-traffic window. Once a year is plenty unless a team member with secret access leaves the company — then rotate immediately.

  1. Choose a window with the lowest traffic. (For a B2B SaaS, Saturday 2 AM your timezone is fine; for a consumer app it doesn’t matter.)
  2. Generate a new secret: openssl rand -base64 32.
  3. Update the env var. Deploy.
  4. All existing sessions immediately invalidate. Users see a “signed out” UI on next request and sign in again.

There is no two-secret iron-session pattern that avoids this. If zero re-logins is a hard requirement, consider storing the session in your own database (custom storage: option on @synqid/js) where you can dual-key the encryption. Most teams don’t bother.

Synq JWKS signing keys

You don’t manage these. Synq rotates the keys that sign access tokens and ID tokens on a regular schedule, publishing the new key to the JWKS endpoint at least 24 hours before issuing tokens signed with it. As long as your JWKS cache TTL is ≤ 24 hours, your verifier finds the new key before any tokens signed with it arrive.

What you must do:

  • Ensure your token verifier honors the Cache-Control header on the JWKS response (openid-client does this).
  • Do not pin a specific kid in your verifier config.
  • Do not cache the JWKS forever (“set and forget”).

If you wrote your own verifier and pinned kid, the next rotation will silently start failing sign-ins. The errors reference lists what that error looks like.

What “no downtime” actually means

The phrase “no downtime” in this guide means no user-visible failure during the rotation window, not zero invalidation of in-flight requests. Specifically:

  • Existing sessions keep working through every rotation on this page — except SESSION_SECRET, where they don’t.
  • Sign-ins in progress during the rotation window succeed because the old credential is still accepted until step 4.
  • Webhook deliveries in flight when the webhook secret rotates succeed because Synq signs with both during the window.
  • Token verification at your backend keeps working because Synq’s signing keys rotate with a 24h overlap.

The window where you could see a failure is the moment the old secret is revoked. If your deployment is on the old secret at revocation time, the next OIDC code exchange fails until you deploy the new one. Mitigation: do step 4 only after you have visually confirmed deployments are on the new secret.

A scriptable rotation flow

The dashboard is fine for one-off rotations. For scheduled rotations, script it against the Synq Admin API:

import { SynqAdminClient } from '@synqid/js' const synq = new SynqAdminClient({ apiKey: process.env.SYNQ_ADMIN_API_KEY!, }) const rotation = await synq.apps.rotateSecret('app_acme_prod') // → { secondarySecret: '...', secondaryRotateAt: '...' } // Push to your secret manager: await secretManager.set('SYNQ_CLIENT_SECRET', rotation.secondarySecret) // Trigger a deploy. Wait for it to roll out. await waitForDeployment() // Promote the new secret as primary. await synq.apps.promoteSecret('app_acme_prod')

The above is the entire programmatic rotation. Wire it into a quarterly cron — secret management without YOLO Friday-night console clicks.


Bookmark this page. Rotation is not a thing you remember off the top of your head — it’s a thing you read step-by-step every time.

Last updated on