default, and it cannot be deleted. Leave the tenant header off and every call lands in default, which is all a single app needs.
When you need more than one
Create a tenant per customer when you are a platform sending on someone else’s behalf, so each customer has their own Apple and Firebase keys, their own subscribers and their own topics. A gym software company gives every gym a tenant; a gym’s members only ever exist inside it.metadata is free-form, which is where your own customer id belongs. Creating a tenant also creates a Default client key for it, which you will find in the workspace key listing.
The default tenant’s slug cannot be changed, and the default tenant cannot be deleted. Deleting any other tenant soft-deletes it and revokes the tenant keys that were issued for it.
Selecting a tenant per request
A workspace key (bk_ws_...) implies its workspace, and BuzzKit-Tenant picks the tenant for that request, the way a Stripe platform passes Stripe-Account with one platform secret key. One key covers every tenant, so there are no per-tenant keys to mint, store or rotate.
Tenant keys
A tenant key (bk_tn_...) is the restricted alternative. It is locked to one tenant’s data plane and needs no header, because it already implies its tenant. It is rejected on workspace-level routes, including tenant management itself. Reach for one when you are handing a customer or a semi-trusted subsystem direct access and want the blast radius to stop at a single tenant.
Settings
Each tenant carries asettings object that a PATCH deep-merges per group. Reads always return the fully resolved object with defaults applied, and an unknown group, key or type is a 400.
channels.<channel>.enabled is a per-tenant kill switch, so you can pause a channel without deleting its credential. identity.requireVerification turns on identity verification for client calls, described below.
The identity secret
A client key ships inside your app binary, so on its own it lets any caller claim anyexternalId. Each tenant has an identity secret that closes that gap. Your backend computes identityHash = HMAC-SHA256(externalId, identitySecret) as hex and hands it to the app at login, and the app passes it on every client call.
A valid hash always marks the subscriber verified, whether or not you enforce it, so verified and anonymous subscribers coexist and you can tell them apart. An invalid hash is always a 401. Turning on identity.requireVerification makes the hash mandatory for every client call in that tenant, and a stolen hash then impersonates only the one subscriber it was minted for.
The tenant object never contains the secret. It lives behind two session-only endpoints that require an admin in the dashboard:
GET /v1/tenants/:tenantSlug/identity-secretreveals it once for your backend’s configuration.POST /v1/tenants/:tenantSlug/identity-secret/rotatereplaces it. Every hash minted with the old secret stops verifying immediately.
Next
Authentication
Key kinds, scopes and the tenant header.
Subscribers
Who lives inside a tenant and how you address them.
iOS SDK
Client keys, identify and the identity hash on the device.
Webhooks
Endpoints belong to the workspace and can be filtered to one tenant.