Devices
Registration, user identifiers, permissions, tags, and token invalidation.
A device is an APNs or FCM token registered to one Carillon app.
Use its id to target it in audience.device_ids.
Registration
The SDK calls POST /v1/devices with a mobile key. Registering the same token in the same app
updates the existing device and returns the same ID. A new token creates a new device.
If you already have a provider token, you can register it with the API or import tokens from CSV.
User identifiers
Set external_id with identify() and clear it on sign-out with clearIdentity()
(or identify(null) in React Native).
- Two devices can have the same
external_id. - Changing the signed-in user updates the identifier on the existing device.
- A reinstall that obtains a new token creates a new device. The old token remains until the provider invalidates it.
external_id is optional. It does not replace the device ID used in a send request.
What registration collects
| Field | Where it comes from |
|---|---|
token | The APNs or FCM token, exactly as the device received it |
platform | ios or android |
environment | Detected by the SDK from the build it is running in, never configured |
external_id | identify(), and null erases it |
tags | setTags(), merged per key; null removes a key |
timezone_id | The IANA identifier the OS reports, never an offset |
locale | BCP 47 |
app_version, app_build | The marketing version, and the monotonic build behind it |
bundle_id | The binary's own identity, and what an APNs topic is checked against |
os_version | Verbatim, never normalised |
push_permission | allowed, denied, provisional or undetermined |
sdk_version | The Carillon SDK's own version |
opted_in | Your app's own opt-out switch, through optIn() and optOut() |
The SDK does not collect advertising identifiers, carrier, IP geolocation, screen metrics, battery state, or device model.
Registration and permission are separate
configure() starts token acquisition and registration without showing a permission prompt.
Call requestPermission() separately when your app should ask to display notifications.
Registration can succeed before permission is granted.
push_permission records the operating system permission. opted_in records your app's preference,
set with optIn() or optOut(). Opting in does not grant operating system permission.
Provider environment
| Platform or build | Environment |
|---|---|
| iOS development-signed build or supported simulator | sandbox |
| iOS TestFlight or App Store build | production |
| Android | FCM; the API retains production for compatibility |
FCM has no sandbox/production endpoint distinction, so the dashboard labels
Android devices FCM. The API's environment: production is a compatibility
value, not an assertion that an Android installation is a production customer.
It does not select Carillon live/test mode or separate your development users.
The SDK detects the APNs environment. APNs sandbox and production tokens are not interchangeable.
If registration fails, inspect debugInfo() and the platform registration error.
Token lifecycle
When APNs reports 410 Unregistered or FCM reports UNREGISTERED, Carillon invalidates the token
and stops sending to it. The device remains visible in the dashboard.
Disabling notification display is separate from token invalidation.
An invalidated device carries invalidated_at and invalidation_reason. The reason is
Superseded when the row lost its token to a newer registration of the same installation, and
otherwise the provider's own word, such as Unregistered or UNREGISTERED. Both are null on a
device that can still be sent to.
For APNs, the feedback timestamp must be later than the device's registration or re-registration. Older feedback does not invalidate a newly registered token.
Tags and opting out
setTags() merges supplied keys with the stored tags. Values can be strings, numbers,
or booleans. Omitted keys remain unchanged; null (nil in Swift) removes a key.
setTag(name, value) updates one key and removeTag(name) deletes one. An empty map is a no-op.
Audience filters can match these values.
Starting with SDK 0.3.0, only pending tag changes are sent. They survive offline use and restart, and are cleared after a successful registration. Later metadata updates do not replay previously acknowledged tags. Earlier SDKs send their full local tag map and can overwrite a server change to the same key.
Your backend can update tags without a push token using a secret key with devices:write:
curl -X PATCH 'https://api.carillon.dev/v1/devices/DEVICE_ID' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{"tags":{"plan":"pro","trial":null}}'A successful response is 200 with the updated device. Read it with GET /v1/devices/DEVICE_ID
to verify the merged tags. The key selects the app and test/live mode. A mobile key cannot
use this endpoint; SDKs update their installation through POST /v1/devices.
The patch does not refresh device activity or change registration metadata.
Concurrent updates to different keys are preserved. For the same key, the last applied
write wins. The resulting map must fit 50 keys and 4,096 bytes; an oversized merge is
rejected without changing the device. On registration, an omitted tags field leaves all
tags unchanged; tags: null explicitly clears all tags.
optOut() excludes the device from sending. Call optIn() to reverse the app-level opt-out.
Operating system notification permission is controlled separately by the user.
Deleting a device
Deletion needs a secret key with the devices:delete scope; a mobile key cannot delete.
| Call | Result |
|---|---|
DELETE /v1/devices/{id} | 204. Idempotent: deleting an unknown or already deleted ID also returns 204 |
DELETE /v1/devices?external_id=user-42 | 200 with { "deleted": n }, every device of that user in the app |
Only the device row is deleted. Its deliveries stay in campaign traces with their platform, and the presence rows that already counted the device toward this month's usage are kept, so usage already consumed remains billed. A token the SDK registers again after deletion creates a new device with a new ID.
Deleting is not opting out: to stop sending to an installation that still exists, use optOut()
or leave the token to be invalidated by the provider. Delete when the user is erased from your own
records, or to remove tokens imported by mistake.
Related pages
SDK overview · Audience filters · Register a device
Stable installation identity
Updated SDKs send the last confirmed device_id and a persisted random
installation_secret on registration. A matching proof lets the API replace the
push token on the same row, preserving attributes omitted from the request.
The server stores only a SHA-256 hash of the secret and never returns it.
An unknown or foreign ID, a missing/incorrect proof, or a different platform or
mode does not authorize reuse of the old row. If the replacement token already
belongs to a device in the same app/platform/mode, that row wins and the previous
row is invalidated. Subsequent registrations use the returned ID. A conflict with
another mode or platform returns 422 device_identity_conflict.
The SDK secret belongs to the installation. Reinstallation can create a new ID.
device_id alone is public context, not authentication. Integrations using older
SDKs continue to register by token.