API Key Security
Key types
A project has three kinds of key, shown in Dashboard → Project → API Keys:
| Type | Purpose | Visibility |
|---|---|---|
| Production | A secret key — never embed it in an app or website. Production React Native apps don’t need one: they authenticate via device attestation instead (see below). | Shown once, at creation. |
| Test | Development and simulator/emulator use. | Shown once, at creation — the CLI fetches and stores it for you. |
| Web (publishable) | Used by the CDN snippet, or as publishableKey when embedding with the npm package. | Public by design — shown in full while active, any time in the dashboard, meant to be readable in your page source. |
Production: attestation, not a bundled secret
In production React Native builds, the SDK does not need a Production key to authenticate. Every request is backed by a session token issued after a successful device attestation (App Attest on iOS, hardware-backed Android Key Attestation on Android). The SDK sends projectId along with the attestation challenge and the attestation submission; the backend resolves your project from projectId and checks that the attested app is registered on it — projectId is required, and it is not a secret. The app’s bundle ID and Team ID (iOS) or signing certificate (Android) must be registered on the project beforehand, or attestation fails. There is no API key to extract from a decompiled app in this path.
If a device doesn’t support attestation, the request fails rather than falling back to an unverified path — except in development, where the SDK detects it’s running in __DEV__ and continues with a stand-in session so you can keep working in a simulator.
Development: the Test key
Test keys skip device attestation so simulators work — that’s why they’re tightly constrained and must never ship:
- Strict, fixed rate limits, plus a daily call cap — independent of your plan and not adjustable from the dashboard.
- Automatic rotation every 30 days. The previous key stops working immediately once rotated; there’s no overlap window.
Before attestation is available, the SDK authenticates with this key. The CLI (qafka init / qafka sync / qafka refresh) writes it into .qafka/qafka-runtime.js, which is gitignored and never committed. The SDK loads it through devConfig, gated behind __DEV__ so Metro removes the require from production builds. See Quick Start and Configuration for the exact wiring.
The dashboard only ever shows a masked value for Test (and Production) keys after creation — the plaintext appears once, in the create dialog. If a Test key leaks, delete or deactivate it on the dashboard’s API Keys page, then run qafka project to issue a fresh Test key into .qafka/qafka-runtime.js (running qafka refresh afterward will otherwise just report that the project has no active Test key).
Web keys: public, with layered restrictions
Web keys (qafka_pk_...) are meant to sit in your website’s HTML — they’re publishable, not secret, the same model as Stripe’s publishable keys. Several layers keep them from being useful outside your own site:
- Origin allowlisting. A chat session can only be started from an origin registered to the project (your site’s domains, or an active embed) — other sites embedding the key can’t open a session. A key can optionally narrow this further to its own allowed domains.
- Scoped to session bootstrap. A Web key can only fetch widget config, start a chat session, and report the widget’s own events — it’s rejected outside those endpoints, so it can’t be used to call other API endpoints.
- Bot checks and rate limits. Sessions started from a Web key go through bot verification and their own strict rate limits.
Limits
How a request authenticates decides which rate-limit check applies:
- Requests with an API key (Production or Test) are checked per minute across three axes scaled from your plan — the API key, the source IP, and the session. Test keys are the exception: they use fixed limits that can’t be raised, plus a daily cap, independent of your plan.
- Keyless native app traffic (device attestation, no API key) is checked against a single per-IP ceiling instead of the three-axis check above.
- Web SDK sessions (no API key, no hardware attestation) get their own stricter limits, scoped per visitor and per project.
See Error Handling › Throttling for the 429 response shape.
Headless usage
The headless path also takes a projectId:
import { QafkaSDK } from '@qafka/react-native'
const sdk = QafkaSDK.getInstance()
await sdk.initialize({ projectId: 'proj_abc123' })See Headless SDK for the full pattern.
Best practices
- Use the CLI for app integration — the Test key never enters your source tree.
- Use Test keys during development so production traffic counters stay clean.
- Restrict Web keys to the exact domains that embed them.
- Replace or revoke Production keys you no longer use.
- Treat all keys as sensitive in CI logs, support tickets, and screenshots.