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 livesBrowser bundleYour server only
Opens a socketYesNo — rejected at the handshake
Subscribes to public-Yes
Subscribes to private- / presence-Only with a signature
Publishes over RESTNoYes
Signs channel authorizationsNoYes
// 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: {} }),
});
A secret key presented from a browser is rejected at the handshake rather than quietly accepted. That is deliberate: it turns the most damaging possible mistake into an error you see on your first run.

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.com does not cover www.example.com — list both, or use *.example.com
  • localhost:3000 does not cover localhost:3001
  • Paths and protocols are ignored — https://example.com/app is stored as example.com

Server-side calls send no Origin header and are not affected; they are gated by the secret key instead.