Skip to main content
A subscriber is one of your users, addressed everywhere by the id you already use for them, so there is no BuzzKit id to store. A subscription is one way to reach that person on one channel: a push subscription is a device, an email subscription is an address. Nothing channel-specific lives on the subscriber itself, so one person can hold several subscriptions per channel, such as two phones or two addresses.

Identify a subscriber

PUT /v1/subscribers/:externalId is an idempotent upsert on your own id. It answers 201 the first time and 200 on every call after, so it is safe on every login.
email is sugar that upserts an email subscription. timezone takes an IANA name and sets $timezone from your backend, which is what scheduling in each subscriber’s local time reads. Anything else is a 400 invalid_timezone.
externalId must be URL-encoded in the path. Emails, slashes and spaces all work as ids.
An identical PUT writes nothing. A create records $subscriber.created on the event stream and a change records $subscriber.updated, both carrying the attributes snapshot.

Attributes

attributes is a free-form JSON object. Segments filter on it, workflows branch on it, and your own reads see it on every subscriber. Two rules apply:
  • The server-side PUT replaces attributes wholesale when the field is present. The client API merges instead, so the app can add keys without wiping what your backend set.
  • The serialized object is capped at 64KB. Past that the call is a 400 attributes_too_large.

System attributes

Keys starting with $ belong to BuzzKit and are refused in a PUT body with a 400 system_attribute. They survive a wholesale replace and ride along in attributes on every read. They refresh on every client identify and every client subscription registration, newest wins. $timezone is the one you can also set from your backend, through the timezone field above, for subscribers whose devices never call the client API.

Subscriptions

A subscription is registered by channel shape, and it creates the subscriber implicitly if the id is new.
Registration is idempotent by tenant, channel and endpoint. Re-registering refreshes lastSeenAt, reactivates an endpoint that was marked invalid, and moves the endpoint if the externalId changed, which is what happens when a device changes hands. You get 201 on create and 200 on a refresh. Every write that is more than a lastSeenAt refresh records $subscription.registered on the stream, and a move also records $subscription.removed for the previous owner. The channel must have a live credential on the tenant, otherwise the call is a 400 channel_not_connected. Push subscriptions also carry environment, production by default and sandbox for debug builds. It selects which APNs credential slot is used at delivery time. In practice the iOS SDK makes this call for you on every launch. Register from your backend when you hold the token yourself.

Muting one device versus removing it

PATCH /v1/subscriptions/:id with { "enabled": false } mutes a single subscription. The work iPhone goes quiet while every other device of the same person keeps receiving. A refresh never resets enabled, so a muted subscription stays muted when the app re-registers. DELETE /v1/subscriptions/:id soft-deletes it instead. The endpoint can register again fresh, and a fresh registration is not muted. A send goes out only through subscriptions that are enabled, active, and opted in to the message’s topic and channel.
status is active or invalid. Delivery feedback from the provider, an APNs 410 or an FCM UNREGISTERED, flips a push subscription to invalid on its own, so dead tokens need no pruning of yours.

Identity verification

A client key alone lets any caller claim any externalId. To prove the claim, your backend computes identityHash = HMAC-SHA256(externalId, identitySecret) as hex and hands it to the app at login. The secret comes from GET /v1/tenants/:slug/identity-secret, which is session-only. Keep it server-side and never ship it in the app binary. POST /v1/tenants/:slug/identity-secret/rotate invalidates every outstanding hash. A valid hash on any client call stamps the subscriber verified with an identityVerifiedAt, both visible on every subscriber read, so anonymous and verified users coexist and you can tell them apart. An invalid hash is a 401 whether or not enforcement is on. Turn enforcement on per tenant and every client call must then carry a valid hash.
With verification off, an attacker can register their own device under someone else’s id. BuzzKit still refuses to move an endpoint that already belongs to another subscriber from an unverified call, but enable verification before you send anything sensitive.

Reading a subscriber

The retrieve embeds the subscriber’s subscriptions along with verified and identityVerifiedAt. GET /v1/subscribers/:externalId/subscriptions returns the same list on its own. The timeline is this person’s slice of the event stream, newest first, keyset-paginated and filterable by name, source and provider. It holds every event you tracked plus the lifecycle BuzzKit writes for them: $subscriber.created, updated and deleted, $subscription.registered, muted, unmuted, removed and invalidated, $preferences.updated and $identify. Every $subscription.* event names the subscription it is about, with externalId, channel, platform and endpoint, so a timeline row can say which device changed. The deliveries list is every delivery addressed to this subscriber, newest first with a total, each carrying a summary of its message: id, channel, topic, title, body and createdAt. It needs the messages:read scope. See delivery for the statuses.

Listing and searching

search matches external ids starting with the text, or a name attribute containing it. Each item carries lastSeenAt, the newest across the subscriber’s live subscriptions and null when there are none, channels such as ["push", "email"], and platforms such as ["ios", "android"]. DELETE /v1/subscribers/:externalId soft-deletes the subscriber and all their subscriptions.

Next

Segments

Target subscribers by attributes, events and activity instead of by id.

Topics and preferences

Let each subscriber choose which notifications reach them, per channel.