Device Onboarding
Automated device enrollment, bootstrap key management, and self-provisioning for IoT devices.
The Onboarding Service (beacontower-onboarding) manages the lifecycle between manufacturing a device and it connecting to your Beacon Tower platform. It handles enrollment groups, bootstrap key distribution, device registration, and provider provisioning — so devices can self-configure on first boot.
Overview
The onboarding flow works like this:
- You create an enrollment group that defines how a class of devices authenticates and where they connect
- You register devices (individually or in bulk) into the enrollment group
- The device calls
GET /get_settingswith its bootstrap key to receive connection settings - You or automation calls
POST /provision_deviceto register the device with the provider
After onboarding completes, the device connects to the MQTT broker using its provisioned credentials and begins publishing telemetry. See MQTT Device Connectivity for the full protocol details.
For higher-security deployments, Beacon Tower also supports certificate-based onboarding using EST (Enrollment over Secure Transport) and the Glaze CA. Certificate-based flows replace username/password authentication with mTLS, supporting three attestation tiers from shared keys to manufacturer birth certificates. See Certificate Onboarding for the full guide.
Key Concepts
Enrollment Groups
An enrollment group defines a class of devices that share:
- Authentication method — Currently
shared_key(bootstrap keys) - Routing target — Where devices should connect (MQTT broker + provider API)
- Response template — JSON template rendered when a device fetches its settings
- Status —
enabledordisabled(disabled groups reject all device requests)
Bootstrap Keys
Each enrollment group has a primary and secondary bootstrap key, encrypted at rest with AES-256-GCM. Devices authenticate using either key, enabling zero-downtime key rotation.
Routing Targets
A routing target defines the infrastructure a device connects to:
- Brokers — MQTT endpoint(s) the device will use (
host:port) - Provider base URL — The provider API that manages device credentials
- Provider API key — Encrypted credential for provider communication
- Webhook secret — For receiving provider callback events
Response Templates
Enrollment groups define a JSON template with {{variable}} placeholders that gets rendered when a device calls GET /get_settings:
| Variable | Source | Type |
|---|---|---|
{{device.id}} | Device record | string |
{{device.primary_key}} | Provider credentials | string |
{{device.secondary_key}} | Provider credentials | string |
{{target.host}} | Routing target broker | string |
{{target.port}} | Routing target broker | integer |
{{target.port}} renders as a JSON integer, not a quoted string.
Authentication
The onboarding service uses three authentication methods:
| Method | Endpoints | Used By |
|---|---|---|
| JWT Bearer | All /api/* endpoints | Administrators and automation |
| Basic Auth | GET /get_settings | Devices (deviceId:bootstrapKey) |
| HMAC-SHA256 | POST /api/webhooks/provider-events | Provider callbacks |
Setup Walkthrough
Prerequisites
- A running Beacon Tower instance with a configured provider (e.g., MQTT Provider)
- The onboarding service deployed and accessible
- A JWT access token with appropriate privileges
export ONBOARDING_URL="https://onboarding.beacontower.ai"
export TOKEN="your-jwt-token"
Step 1: Create a Routing Target
A routing target tells devices where to connect and tells the onboarding service how to reach the provider API.
curl -X POST $ONBOARDING_URL/api/routing-targets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production IoT Hub",
"brokers": [
{ "host": "your-iot-hub.azure-devices.net", "port": 8883 }
],
"provider_base_url": "https://api.beacontower.ai/providers/iothub-primary",
"provider_api_key": "your-provider-api-key",
"webhook_secret": "your-webhook-secret"
}'
Response:
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "Production IoT Hub",
"brokers": [{ "host": "your-iot-hub.azure-devices.net", "port": 8883 }],
"created_at": "2025-01-15T10:00:00Z"
}
Save the routing target id for the next step.
Step 2: Create an Enrollment Group
An enrollment group ties together authentication, routing, and the response template that devices receive.
export ROUTING_TARGET_ID="f47ac10b-58cc-4372-a567-0e02b2c3d479"
curl -X POST $ONBOARDING_URL/api/enrollment-groups \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Temperature Sensors",
"auth_method": "shared_key",
"routing_target_id": "'$ROUTING_TARGET_ID'",
"status": "enabled",
"response_template": {
"mqtt_host": "{{target.host}}",
"mqtt_port": {{target.port}},
"device_id": "{{device.id}}",
"primary_key": "{{device.primary_key}}",
"secondary_key": "{{device.secondary_key}}"
}
}'
Response:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Temperature Sensors",
"auth_method": "shared_key",
"status": "enabled",
"routing_target_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"created_at": "2025-01-15T10:05:00Z"
}
Step 3: Generate Bootstrap Keys
Generate the primary bootstrap key for the enrollment group:
export GROUP_ID="a1b2c3d4-e5f6-7890-abcd-ef1234567890"
curl -X POST $ONBOARDING_URL/api/enrollment-groups/$GROUP_ID/keys/generate \
-H "Authorization: Bearer $TOKEN"
Response:
{
"primary_key": "bk_a3f8e2d1c4b5...",
"secondary_key": null
}
The first call fills primary_key. Subsequent calls fill secondary_key, enabling zero-downtime rotation. Distribute the key to devices during manufacturing or initial configuration. You can retrieve current keys at any time via GET .../keys.
Step 4: Register Devices
Single Device
curl -X POST $ONBOARDING_URL/api/enrollment-groups/$GROUP_ID/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"device_id": "temp-sensor-001"
}'
Bulk Import (NDJSON)
For large batches (up to 10,000 devices per request), use the bulk endpoint with NDJSON format:
curl -X POST $ONBOARDING_URL/api/enrollment-groups/$GROUP_ID/devices/bulk \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/x-ndjson" \
-d '{"device_id": "temp-sensor-001"}
{"device_id": "temp-sensor-002"}
{"device_id": "temp-sensor-003"}'
Response:
{
"added": 3,
"errors": [],
"skipped": 0
}
Each device is validated against the provider in parallel (max 20 concurrent). Devices that already exist are skipped, and validation failures are reported in the errors array.
Step 5: Device Bootstrap
On first boot, the device calls GET /get_settings with Basic Auth (deviceId:bootstrapKey):
# This is what the device firmware does:
curl $ONBOARDING_URL/get_settings \
-u "temp-sensor-001:bk_a3f8e2d1c4b5..."
Response (rendered from the enrollment group's template):
{
"mqtt_host": "your-iot-hub.azure-devices.net",
"mqtt_port": 8883,
"device_id": "temp-sensor-001",
"primary_key": "device-specific-key-from-provider",
"secondary_key": "device-specific-secondary-key"
}
The device now has everything it needs to connect to the MQTT broker. The onboarding service fetches the device's credentials from the provider behind the scenes.
Step 6: Provision the Device
Provisioning registers the device with the provider infrastructure (e.g., creates it on IoT Hub):
curl -X POST $ONBOARDING_URL/provision_device \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"device_id": "temp-sensor-001",
"enrollment_group_id": "'$GROUP_ID'"
}'
Response (201 Created for new enrollments, 200 OK if already provisioned):
{
"device_id": "temp-sensor-001",
"enrollment_group_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"provider_client_created": true,
"enrollment_created": true,
"provisioned_at": "2025-01-15T10:30:00Z"
}
Provisioning (POST /provision_device) registers the device on the provider — this is typically done by your automation or management system. Bootstrap (GET /get_settings) is called by the device itself to fetch its connection settings. A device must be provisioned before it can successfully bootstrap.
Bootstrap Key Rotation
The two-key model enables zero-downtime key rotation:
┌─────────────────────────────────────────────────────┐
│ 1. Initial state: primary = key_A, secondary = — │
│ 2. Generate: primary = key_A, secondary = key_B │
│ (both keys valid, start distributing key_B) │
│ 3. Flip: primary = key_B, secondary = — │
│ (key_A invalidated, all devices use key_B) │
└─────────────────────────────────────────────────────┘
Rotation Workflow
1. Generate a new key into the secondary slot:
curl -X POST $ONBOARDING_URL/api/enrollment-groups/$GROUP_ID/keys/generate \
-H "Authorization: Bearer $TOKEN"
At this point both keys are valid. Devices can authenticate with either one.
2. Distribute the new key to devices (firmware update, configuration push, etc.).
3. Flip — promote secondary to primary:
curl -X POST $ONBOARDING_URL/api/enrollment-groups/$GROUP_ID/keys/flip \
-H "Authorization: Bearer $TOKEN"
The old primary key is now invalidated. All devices must use the new key.
4. Verify current key status:
curl $ONBOARDING_URL/api/enrollment-groups/$GROUP_ID/keys \
-H "Authorization: Bearer $TOKEN"
Flip is irreversible — the old primary key is permanently invalidated. Ensure all devices have received the new key before flipping.
Webhook Integration
The onboarding service can receive events from providers via webhooks. Currently supported:
| Event | Behavior |
|---|---|
device.deleted | Soft-deletes the device record in the onboarding database |
Webhook Setup
When creating a routing target, provide a webhook_secret. The provider sends events to:
POST /api/webhooks/provider-events
Each request includes an X-Webhook-Signature header containing the hex-encoded HMAC-SHA256 of the request body, signed with the webhook secret. The onboarding service validates this signature using constant-time comparison before processing any event.
API Reference
Enrollment Groups
| Method | Route | Description |
|---|---|---|
| POST | /api/enrollment-groups | Create enrollment group |
| GET | /api/enrollment-groups/{id} | Get enrollment group |
| GET | /api/enrollment-groups | List enrollment groups (paginated) |
| PUT | /api/enrollment-groups/{id} | Update enrollment group |
| DELETE | /api/enrollment-groups/{id} | Delete enrollment group |
Bootstrap Keys
| Method | Route | Description |
|---|---|---|
| POST | /api/enrollment-groups/{id}/keys/generate | Generate bootstrap key |
| POST | /api/enrollment-groups/{id}/keys/flip | Rotate keys (secondary to primary) |
| GET | /api/enrollment-groups/{id}/keys | Get current keys |
Devices
| Method | Route | Description |
|---|---|---|
| POST | /api/enrollment-groups/{id}/devices | Add single device |
| POST | /api/enrollment-groups/{id}/devices/bulk | Bulk add devices (NDJSON, max 10k) |
| DELETE | /api/enrollment-groups/{id}/devices/{deviceId} | Remove device |
| GET | /api/enrollment-groups/{id}/devices | List devices (paginated) |
Routing Targets
| Method | Route | Description |
|---|---|---|
| POST | /api/routing-targets | Create routing target |
| GET | /api/routing-targets/{id} | Get routing target |
| GET | /api/routing-targets | List routing targets (paginated) |
| PUT | /api/routing-targets/{id} | Update routing target |
| DELETE | /api/routing-targets/{id} | Delete routing target |
Provisioning & Bootstrap
| Method | Route | Auth | Description |
|---|---|---|---|
| POST | /provision_device | JWT | Provision a device with the provider |
| GET | /get_settings | Basic | Device fetches connection settings |
Webhooks
| Method | Route | Auth | Description |
|---|---|---|---|
| POST | /api/webhooks/provider-events | HMAC-SHA256 | Receive provider events |
Pagination
List endpoints use cursor-based pagination:
GET /api/enrollment-groups?limit=50&cursor=<continuation_token>
The response includes a continuation_token field. Pass it as cursor in the next request. When null, there are no more results.
Error Responses
All errors return a consistent format:
{
"error": "error_code",
"message": "Human-readable description"
}
Error messages are sanitized — internal details are never exposed.
Next Steps
- MQTT Device Connectivity — What happens after onboarding: MQTT connection, telemetry, twins, and commands
- MQTT Protocol Reference — Topic structure, payload formats, and a complete device code example
- Assets and Bindings — Bind assets to provisioned devices
- Your First Asset via API — End-to-end walkthrough of creating and binding an asset
- Firmware Management — Push firmware updates to onboarded devices