Skip to content

Payload signing

Every payload FortressFlag sends to one of your apps carries a signature, and every SDK checks it before a single flag value changes. You don’t have to do anything to get this — the SDKs ship with the production public key built in and verify by default. This page explains what that buys you, what the two policy settings mean, and which keys your apps trust today.

When your app polls, FortressFlag answers with two things: the payload (your device’s flag values on the client plane, the full ruleset on the server plane) and a signature over the exact bytes of that payload. The signature is made with a private key only FortressFlag holds; your SDK checks it with the matching public key it shipped with.

If the bytes were changed anywhere between our servers and your app — a proxy, a CDN, a cache file someone edited on the device — the check fails and the SDK ignores the response. It keeps serving the last values it verified (see the fallback promise) rather than trusting bytes it can’t vouch for.

Two things the signature does not do, so nobody over-relies on it:

  • It doesn’t hide anything. The payload isn’t encrypted — a client key is public by design, and so is everything served against it. Don’t put secrets or your users’ personal data in rules.
  • It doesn’t replace TLS. The SDKs talk to FortressFlag over HTTPS as well; the signature is the second lock, for the parts of the path TLS doesn’t cover (caches, on-device storage).

Every SDK has a signature policy with two settings:

  • required — the default. The SDK verifies every payload against its trusted keys and rejects anything unsigned, signed with a key it doesn’t know, or signed over different bytes. This is what you want in every build that talks to FortressFlag.
  • disabled — accept unsigned payloads. It exists for one situation: running against a local FortressFlag backend that has no signing key configured. It’s a named, greppable setting rather than a silent fallback, so “why isn’t this verifying?” always has an answer in your own source. Never ship a production build with it.

There’s no “warn” mode in between. A payload either verifies or it’s ignored.

FortressFlag signs with one key per environment, and the SDKs ship trusting the production key only. If you test against staging (fortressflag.net), add the staging key to your SDK’s trusted keys explicitly.

Environment Key id Public key (base64url)
Production (fortressflag.com) prod-2026-09-k1 EaEF8MHNu3onHxemTg3-OcrKrq7ODsZIVEp-IVV2ojg
Staging (fortressflag.net) staging-2026-09-k1 RHFhzt8i3hQ4NKv_IrB92YuXmyNfDXx8eWN8cOrEBc8

The same keys as raw bytes, in hex, if that’s what your configuration takes:

prod-2026-09-k1 11a105f0c1cdbb7a271f17a64e0dfe39cacaaeaece0ec648544a7e215576a238
staging-2026-09-k1 447161cedf22de143834abff22b07dd98b979b235f0d7c7c79637c70eac405cf

Each key is a 32-byte Ed25519 public key. The key id is the middle part of every signature (ed25519:prod-2026-09-k1:…), which is how the SDK knows which key to check with. These values are public — you can paste them into a config file or a repository without a second thought.

When FortressFlag rotates a key, the new public key ships in an SDK release first, and we only start signing with it once the SDKs in the wild trust it. Rotation never requires you to release an app on a deadline: an SDK that trusts both the old and the new key keeps verifying throughout.

The web SDK verifies with your browser’s built-in Web Crypto. That needs Ed25519 support — Safari 17+, Chrome 137+, Firefox 130+ — and a secure context: https:// pages and http://localhost. On a plain http:// page served from, say, a LAN address, browsers don’t expose Web Crypto at all.

Where the browser can’t verify, the SDK doesn’t pretend it did and doesn’t throw a good cache away either. It reports the payload as unverifiable, keeps serving its last verified values or your code’s defaults, and logs the reason once. Serve your app over HTTPS and this never comes up.