Keys & rotation
Every app has two keys. Which one you reach for depends entirely on whether the code runs in a browser or on your server.
The two keys
pk_app_a1b2c3d4e5f6a7b8_KOyHrgOjw4GoBFe0WVVjBq
sk_app_a1b2c3d4e5f6a7b8_V4hJ2qXm...
└────── app id ──────┘ └─ secret ─┘Both embed the app id, so we can verify a key with a single lookup rather than checking it against every app on the platform.
Public pk_ | Secret sk_ | |
|---|---|---|
| Where it lives | Browser bundle | Your server only |
| Opens a socket | Yes | No — rejected at the handshake |
Subscribes to public- | Yes | — |
Subscribes to private- / presence- | Only with a signature | — |
| Publishes over REST | No | Yes |
| Signs channel authorizations | No | Yes |
// Browser — public key
const socketly = new Socketly({ key: process.env.NEXT_PUBLIC_SOCKETLY_KEY! });
// Server — secret key
await fetch(`https://api.socketly.co/v1/apps/${APP_ID}/events`, {
method: 'POST',
headers: { authorization: `Bearer ${process.env.SOCKETLY_SECRET}` },
body: JSON.stringify({ channels: ['public-feed'], event: 'tick', data: {} }),
});Rotating a key
Rolling issues a new key immediately and keeps the current one working for a grace window you choose. Both are valid at once, so you deploy in between and nothing drops.
- 24 hours — the normal choice. Roll, deploy, done.
- 1 hour — when you want the old key gone sooner.
- Revoke now — for a leaked key. Everything still using it stops working the moment you confirm.
Signatures already minted with the old secret keep verifying for the whole window, so in-flight subscriptions are not interrupted either.
If you lose the secret
It is shown once, when the app is created or the key is rolled. We store it encrypted and cannot display it again — so if it is gone, roll the key and take the new one. Choose “Revoke now” if you think it leaked rather than simply being misplaced.
Allowed origins
A public key only works from the origins you list on the app. Matching is on exact hostname, so:
example.comdoes not coverwww.example.com— list both, or use*.example.comlocalhost:3000does not coverlocalhost:3001- Paths and protocols are ignored —
https://example.com/appis stored asexample.com
Server-side calls send no Origin header and are not affected; they are gated by the secret key instead.