API keys
Three kinds of credentials, three threat models.
pk_ — Publishable
- Designed to ship to the browser. Sent as the
X-Client-IDheader on every signed B2B request, also embedded in the SDK bundle for client-side checkout opens. - Can identify your account; cannot create sessions, read other merchants’ data, or trigger anything destructive.
- A leaked publishable key is a low-severity event.
sk_ — Secret (HMAC signing key)
- The HMAC-SHA256 signing key for all B2B API calls — see Authentication.
- Never travels over the wire. Only its derived per-request signature does. So you only have to worry about leaks at the storage layer (env vars, git, logs), not at the transport layer.
- Server-only. Should never appear in a browser bundle, public repo, screenshot, or chat message.
- A leaked secret is a high-severity event.
whsec_ — Webhook signing secret
- Used to verify the signature on inbound webhook deliveries from us to your server. See Signature verification.
- Separate per webhook endpoint — if you have 3 registered endpoints,
you have 3 distinct
whsec_secrets. Environment is encoded in the prefix:whsec_live_…/whsec_test_…. - Server-only. Like
sk_, never travels over the wire — only used to verify HMACs locally. - Rotation has a 24-hour grace window. Click Rotate and the
previous secret stays accepted for 24h alongside the new one
(deliveries carry both
X-SignatureandX-Signature-Prev), so you can redeploy your verifier without holding traffic. - Reveal-existing-secret is available, gated by fresh 2FA and recorded in the audit log — for the case where the secret was lost and rotation isn’t acceptable. The dashboard’s default posture is “rotate, don’t reveal”.
- A leaked webhook secret lets an attacker fake events to your URL. Medium-to-high severity depending on how much you trust the event payload.
Scopes
Secret keys are scoped. The dashboard lets you mint keys with one of these scope bundles:
| Scope | Can do | Used for |
|---|---|---|
read | List/read orders, sessions, refunds, balances | Read-only integrations (analytics, BI) |
write_order | All read + create sessions, create orders, cancel orders | Storefront backend |
write_refund | All read + create refunds, mark refunds executed | Customer support tools |
webhook_manage | All read + manage webhook endpoints | DevOps tooling |
A default-mint “full access” key gets all four. Minting per-purpose keys is still good hygiene — it documents intent and gets you ready for enforcement when it lands — but read the caveat below before you treat scope as a security boundary.
Scopes are advisory today — they are not enforced at the gateway.
The gateway verifies the key’s HMAC signature and injects your
merchant identity (X-Merchant-ID / X-Merchant-Domain) to
downstream services, but it does not propagate or check the
key’s scope. In practice that means a leaked sk_ of any scope can
call any /b2b/v1/* endpoint for your merchant — a read key is
not actually prevented from creating a refund. So narrow scopes do
not limit blast radius yet: for breach planning treat every
secret key as full-access, and rely on fast rotation + revocation
(below) as your real containment. Per-scope enforcement is on the
roadmap.
Rotation
- Generate a new key. Dashboard → Developers → API keys → + Add key. Pick scope. The dashboard displays the secret once — store it immediately.
- Roll your env vars to the new value across all environments. Deploy.
- Verify traffic. Dashboard shows per-key request counts in real time. Wait for the old key’s count to drop to zero.
- Revoke the old key. Same screen → kebab menu → Revoke.
There is no automatic overlap window today — once you revoke a
key, any in-flight request signed with it gets 401. Plan your
rotation accordingly: deploy the new key first, drain traffic from
the old, then revoke.
Emergency revocation
If a key has leaked (in git history, in a public bundle, in a logged stack trace, in a partner’s pen-test report) — revoke it immediately, even at the cost of some failed requests. Better to fail loudly than to let an attacker hold a valid credential.
Steps:
- Dashboard → Developers → API keys → [key] → Revoke now. Effect is instant; no grace period.
- Mint a replacement and deploy.
- Audit recent activity — dashboard shows the last 30 days of requests per key with IPs and endpoints hit.
If you suspect the breach is broader than one key, contact [email protected] to:
- Get a full audit log export for your merchant account
- Rotate webhook secrets in bulk
- Optionally freeze the account while you investigate
Storage best practices
- Env vars only. Never commit secrets to git, even in a
.env.examplethat says “REPLACE ME”. - Per-environment keys. Different
sk_test_…andsk_live_…for dev/staging/prod, sourced from your secrets manager (AWS Secrets Manager, Vault, Doppler, …). - Restrict env var access. In Kubernetes, mount as a
Secret, not aConfigMap. In Vercel/Netlify, use environment-variable scoping not project-wide globals. - Don’t log requests with bodies. Even when debugging — your
HMAC signature in
X-Signatureis single-use but your business payload may include PII.
What’s NOT supported today
- ❌ IP allowlisting for secret keys. On the roadmap.
- ❌ OAuth-style scoped per-user tokens. The current key model is per-merchant, not per-user.
- ❌ Automatic key rotation (e.g. weekly rotation enforced by the platform). Manual today.
What’s next
- Authentication — exact signing algorithm for B2B calls.
- Webhooks → Signature verification
— how
whsec_is used on incoming events.