Security & keys
The two-key model
| Client key ck_live_ / ck_test_ | Server key sk_live_ / sk_test_ | |
|---|---|---|
| Lives | Browsers, mobile apps — public by design | Your servers only |
| Can | Register devices, identify (verified), track events, read that user's own inbox | Everything: send, read, segments, users |
| Cannot | Send anything, read any list, touch config — structurally, not by policy | — |
| If leaked | Worst case: junk registrations in your app (rate-limited, hygiene-culled) | Rotate immediately — mint a new key, revoke the old |
Anyone can decompile your app and extract the client key — the design assumes it, the
same way Stripe's publishable keys do. Our secret-scanning pattern
(sk_(live|test)_[A-Za-z0-9_-]{43}) deliberately matches only server keys, so
GitHub flags real leaks without false-alarming on intentionally-public client keys.
Identity verification (do this)
The one meaningful client-side attack: calling
identify("victim-id") with an extracted client key to receive someone else's
notifications. Close it in five minutes:
- Dashboard → Setup → Generate identify secret (shown once, store server-side).
- Your server signs each logged-in user:
hex(hmac_sha256(secret, external_id))— or justsignIdentify()from@notibase/node. - Frontend passes the signature to
identify().
Once a secret exists, unsigned or wrongly-signed identify calls are rejected (403, constant-time comparison). Rotating the secret invalidates all outstanding signatures.
Your push credentials
APNs .p8 keys, FCM service accounts, and BYO LLM keys are validated at upload (a malformed key is rejected with the reason, before it can ever fail a send), encrypted at rest, and never returned by any API. Uploading replacements auto-revokes the previous set. Delete a credential and sends over that channel stop immediately.
Tenant isolation
Every query is scoped to your app at the application layer, backed by Postgres row-level security bound per-request, and the isolation is enforced by a cross-tenant probe suite that runs in CI on every change — a regression is a failed build, not an incident. Delivery logs, devices, users, segments: another tenant's key or session gets 404s and empty lists, never your data.
Sending discipline (protects your users and your reputation)
| Idempotency keys | Retries and double-clicks can never double-notify. |
| Rate limits & quotas | A runaway loop 429s before it becomes a reputation event. |
| Token hygiene | Dead tokens expire on first bounce; counts stay honest. |
Reporting
Vulnerabilities: [email protected] — we respond fast and credit reporters. Abuse by a Notibase customer: [email protected].