How do Trusted Entitlements (response signing) work with RevenueDot?
The RevenueCat SDKs can check that each response really came from the server, by verifying an Ed25519 signature against a public key built into the SDK. RevenueDot signs its responses in exactly that format when you set REVENUEDOT_SIGNING_KEY. The stock SDK only trusts RevenueCat's key, so against RevenueDot it reports verification FAILED. Turn verification off in the stock SDK, or use a RevenueDot fork that trusts your key.
What each setup gives you#
| App uses | Server | Result |
|---|---|---|
Stock RevenueCat SDK, verification DISABLED |
any | No check. Everything works. Recommended for proxy mode |
Stock SDK, INFORMATIONAL (the iOS and Android default) |
any | Entitlements work, but each carries verification: FAILED and the SDK logs an error |
Stock SDK, ENFORCED |
any | Every request fails. Never use it against RevenueDot |
| RevenueDot fork (official build) | RevenueDot Cloud | VERIFIED |
| RevenueDot fork built with your public key | your server with REVENUEDOT_SIGNING_KEY |
VERIFIED |
Defaults of the stock SDKs: iOS and Android are informational; Unity defaults to informational in the Inspector; Capacitor passes no mode, so the native default applies; React Native, Flutter and Kotlin Multiplatform default to disabled; Cordova has no setting and logs the failure; purchases-js does not verify. Each SDK guide shows the exact setting. RevenueCat describes the feature in its Trusted Entitlements docs.
Turn signing on in your server#
- Generate a key pair in a checkout of the server repository:
Shell
pnpm tsx scripts/signing-keygen.tsTextREVENUEDOT_SIGNING_KEY=3q2+7w...base64 of a 32-byte seed...= public key: ZzwPxGlon0E8ErpDh9QAH0Jh6+E6D6qufvTSetXZY9Y= - Give the first line to the server as an environment variable, and keep it in your password manager. See Self-hosting. Anyone with the seed can sign responses your apps trust.
- Restart and check the public key:
Shell
curl -s http://localhost:8787/.well-known/revenuedot-signing-keyWithout a key this answers 404 and responses are not signed.JSON{"algorithm":"Ed25519","public_key":"ZzwPxGlon0E8ErpDh9QAH0Jh6+E6D6qufvTSetXZY9Y=","encoding":"base64","header":"X-Signature","docs":"https://revenuedot.app/docs"}
What gets signed#
Every 2xx and 3xx response under /v1 and /rcbilling carries an X-Signature header. The SDK sends a random X-Nonce with requests it verifies, and the nonce is part of the signed message. The value is base64 of 180 bytes, the layout the iOS and Android SDKs verify (apps/server/src/services/signing.ts):
| Bytes | Content |
|---|---|
| 0 to 31 | An intermediate Ed25519 public key |
| 32 to 35 | The intermediate key's expiry, in days since 1970-01-01, little-endian |
| 36 to 99 | The root key's signature over the expiry and the intermediate key |
| 100 to 115 | A random salt |
| 116 to 179 | The intermediate key's signature over the message |
The message is the salt, the API key, the nonce, the request path, the X-Post-Params-Hash and X-Headers-Hash request headers, the X-RevenueCat-Request-Time and X-RevenueCat-ETag response headers, and the body. The server makes a new intermediate key every 30 days (7 days before the old one expires) and keeps it in memory; only the root seed is configured.
The server's contract tests check this format against real RevenueCat signatures published in the purchases-ios test suite, and check that tampered bodies, nonces, paths, keys and expired intermediate keys are rejected.
Verify against your own server#
The official forks trust RevenueDot Cloud's public key (gXdn2hmqR/TbdtQwK02laE0YgFz0Rtf918LICLrgZhg=). A self-hosted server cannot sign with that key. To get VERIFIED against your own server, build the forks with your public key and, optionally, your host. The fork pipeline in the server repository does it:
# In a checkout of revenuedot/revenuedot, with the fork repositories cloned next to it.
pnpm tsx scripts/forks/apply.ts --all \
--var apiHost=https://revenuedot.example.com \
--var signingPublicKey=ZzwPxGlon0E8ErpDh9QAH0Jh6+E6D6qufvTSetXZY9Y=
# or: REVENUEDOT_FORK_API_HOST=... REVENUEDOT_FORK_SIGNING_PUBLIC_KEY=... pnpm tsx scripts/forks/apply.ts --allThen build and ship those SDK builds. Rotating the root key means shipping new SDK builds, because the key is compiled into the app. Choose it once and guard it.