Authentication & API Keys
The Hober REST API base URL is https://api.hober.io. Every request is
authenticated with one of three credential types, each scoped to what that
kind of caller should be able to do.
Credential types at a glance
| Credential | Format | Sent as | Secrecy | Typical caller |
|---|---|---|---|---|
| Client SDK key | sk_… | X-SDK-Key header | Publishable | Mobile / browser SDKs embedded in your app |
| Server key | hober_srv_… | Authorization: Bearer | Secret | Your backend (event tracking, export, REST hooks) |
| Dashboard session token | JWT | Authorization: Bearer | Secret, short-lived | The Hober dashboard on behalf of a logged-in user |
Client SDK key
The client SDK key is a publishable, workspace-scoped key designed to be
shipped inside mobile apps and web pages. Its format is a sk_ prefix, an
encoded workspace identifier, and an HMAC-SHA256 signature:
sk_XXXXXXXXXXXXXXXX.YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
The signature is embedded in the key itself — requests are not individually signed. Send the key verbatim on every request:
X-SDK-Key: YOUR_SDK_KEY
Authorization: SdkKey YOUR_SDK_KEY is accepted too. The client-key routes
answer CORS from any origin, so the browser SDK can call them from your own
site.
You can copy your workspace's SDK key from the dashboard under Settings → API keys. The key is stable for the life of the workspace: the same key is returned every time you view it, and there is currently no self-serve rotation.
What the client key can call
Because the key is extractable from a shipped app, it only grants write-forward, device-facing operations:
POST /api/v1/subscribers/upsert— create or update a subscriberPOST /api/v1/devices— register a device push tokenPOST /api/v1/notifications/interactions— record opens, taps, and clicksPOST /v1/eventsandPOST /v1/events/identify— behavioral event ingestion (client tier — see Events for tier limits)- The in-app message delivery stream used by the SDKs
It cannot read or export data, send notifications, or manage any workspace
resource. A missing or invalid key returns 401 with code UNAUTHORIZED.
Identity verification
The client key proves which workspace is calling, not which user. To prove the
user, your backend signs each user's external_id with your workspace's
identity secret, and your app sends that signature as a header:
X-Hober-Identity: hex(HMAC-SHA256(identity_secret, external_id))
Compute it on your server, never in the app, and hand it to the app with the user's session. Each server SDK has a helper; without one, for example in Node.js:
const token = crypto.createHmac('sha256', IDENTITY_SECRET).update(user.id).digest('hex')
Generate the secret in the dashboard under Settings → Identity
verification (owners and admins), or with
POST /api/v1/account/identity-verification/rotate. It is shown once. The
same screen switches the mode and shows, for the last 7 days, how many calls
had no valid signature: what enforce would reject
(GET /api/v1/registry/identity-report?days=7). Rotating again
keeps the previous secret valid for 72 hours so you can redeploy.
GET /api/v1/account/identity-verification shows the current mode and
PUT with {"mode": "off" | "report" | "enforce"} changes it:
| Mode | Writes to an existing subscriber without a valid token |
|---|---|
report (default) | accepted and counted, so you can see what enforce would reject |
enforce | rejected; device registration and the in-app stream and inbox need the token too |
off | accepted, not counted |
The in-app stream and inbox (/v1/in-app/stream, /v1/in-app/messages) take
the token for the subscriber in subscriber_id. Under enforce they refuse a
call without it, because the client key alone would let anyone read another
user's messages by their id. The SDKs' in-app clients send the token you passed
to identify.
Events sent with the client key follow the same modes. Under enforce, an
event naming a subscriber_id the token does not verify is detached from that
subscriber: it is kept as an anonymous event when it carries an
anonymous_id, and dropped otherwise. The request still returns 202, so it
reaches neither the subscriber's profile nor their journeys. Linking an
anonymous_id to a subscriber (POST /v1/events/identify) without a valid
token returns 403. Server keys are trusted and never need the token.
In every mode, a call without a valid token cannot replace or clear an
existing subscriber's email, and cannot record consent for an email the
subscriber already has. The first is rejected; the consent is ignored.
Creating a new subscriber, or setting the first email on one that has none,
never needs a token. A rejected call returns 403 with code
IDENTITY_VERIFICATION_REQUIRED.
If Hober briefly cannot check signatures, a workspace in enforce fails
closed wherever Hober has already seen its mode (almost always): a write to an existing subscriber, a device registration and
POST /v1/events/identify return 503 with code
IDENTITY_VERIFICATION_UNAVAILABLE and a Retry-After header, rather than
being accepted unverified. The Hober SDKs wait and retry these calls up to
three times before reporting the error; if you call the API directly, retry
after Retry-After. In report and off, the call goes through unverified.
Server keys
Server keys are secret, revocable credentials for server-to-server calls.
Their format is the hober_srv_ prefix followed by 32 URL-safe base64
characters. Send them as a bearer token:
Authorization: Bearer hober_srv_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Create and revoke server keys in the dashboard under Settings → API keys (requires a role with credential-management permission). The full key is shown exactly once at creation — only its prefix is displayed afterwards, and the key is stored as a one-way hash.
Scopes
Each server key carries a set of scopes with the shape
domain:read / domain:write. Keys created without explicit scopes get the
default set:
| Scope | Grants |
|---|---|
events:write | Event ingestion (POST /v1/events, /v1/events/identify) — the default scope |
subscribers:read | Subscriber export (GET /v1/subscribers/export): every subscriber with email, consent status and attributes |
subscribers:write | Server identify (POST /v1/subscribers/identify): create or update subscribers by external_id, claiming a subscription-form signup with the same email |
A server key that reaches an endpoint without its scope receives 403 with
code FORBIDDEN. Because subscribers:read exposes every subscriber's
personal data, and subscribers:write can take over any form signup by
email, only an owner or admin can create a key that carries either.
What server keys can call
POST /v1/events,POST /v1/events/identify— server-tier event ingestion, includingexternal_idactor resolution and larger batch limitsGET /v1/events/export— bulk event history exportGET /v1/subscribers/export— subscriber export for syncing systems (subscribers:read)POST /v1/subscribers/identify— identify a signed-in user and claim their form signup (subscribers:write)GET /v1/whoami— the identity of the presented keyPOST /v1/hooks,GET /v1/hooks,DELETE /v1/hooks/:id— REST hook (webhook subscription) management, as used by the Zapier integrationGET /v1/subscribers— a paged subscriber listing for integrations
See the Server-Side Event Tracking guide for choosing between the client and server tier.
Revocation
Revoking a key takes effect immediately for most surfaces; the event ingestion path caches successful validations briefly, so a revoked key can keep ingesting for up to about one minute.
Dashboard session tokens
Management surfaces — notifications, campaigns, templates, schedules,
segments, journeys, channels, webhooks, team, and billing — are
authenticated with the short-lived JWT bearer tokens that the dashboard
obtains when a user logs in. Access tokens expire after 15 minutes and are
refreshed transparently by the dashboard session; role-based permissions on
the logged-in user additionally gate what each token may do (for example,
read-only tokens receive 403 with code READ_ONLY_TOKEN on write
requests).
There is currently no long-lived personal access token for the management
API. For programmatic server-to-server access, use a server key (events,
export, and REST hooks) — or agent access over MCP, which uses its own
hober_mcp_ key type; see the
Agent access guide.