Configuration
All SDK configuration is loaded from the standard .NET configuration sources (appsettings.json, environment variables, secrets). AddBeaconTowerProvider<TAdapter> binds the sections below at startup.
Provider identity
{
"Provider": {
"Id": "my-provider"
}
}
Outbound integrations (OAuth2 client-credentials)
Aggregator-backed providers register named outbound integrations with
services.AddOutboundOAuth2Client<TClient>(configuration, name). Each
named integration has its own configuration block:
{
"Outbound": {
"<name>": {
"TokenUrl": "https://...",
"ClientId": "<from secret store>",
"ClientSecret": "<from secret store>",
"Scope": null,
"Audience": null,
"RefreshSkewSeconds": 60
}
}
}
| Option | Default | Description |
|---|---|---|
TokenUrl | (required) | OAuth2 token endpoint URL. |
ClientId | (required) | Client identifier. Source from secret store. |
ClientSecret | (required) | Client secret. Source from secret store. |
Scope | null | Optional space-separated scope list. |
Audience | null | Optional audience parameter (some providers require it). |
RefreshSkewSeconds | 60 | Refresh tokens this many seconds before expiry. |
See Outbound Integrations for the full pattern.
| Option | Default | Description |
|---|---|---|
Id | configured by host (e.g. "mqtt") | Unique provider identifier. Used in NATS subjects, telemetry headers, CloudEvent source. |
ProviderSdkOptions is set from the Action<ProviderSdkOptions> you pass to AddBeaconTowerProvider. The reference host reads Provider:Id from configuration and forwards it.
Database
{
"ConnectionStrings": {
"beacontower-mqttproviderdb": "Host=localhost;Database=mqtt;Username=postgres;Password=..."
}
}
The SDK uses BeaconTower.Data.PostgreSql with Dapper. DbUp migrations run at startup unless ASPNETCORE_ENVIRONMENT is Test or Testing.
NATS
{
"Nats": {
"Url": "nats://localhost:4222",
"User": "",
"Password": "",
"TlsCertFile": "",
"TlsKeyFile": "",
"MonitoringPort": 8222,
"MonitoringTimeoutSeconds": 5,
"ClusterReplicaThreshold": 3,
"Events": {
"ServiceName": "my-provider"
}
}
}
| Option | Default | Description |
|---|---|---|
Url | "nats://localhost:4222" | NATS server URL. |
User / Password | empty | Optional NATS authentication. |
TlsCertFile / TlsKeyFile | empty | Optional client TLS material. |
MonitoringPort | 8222 | NATS monitoring HTTP port — queried at startup to detect cluster topology. |
MonitoringTimeoutSeconds | 5 | HTTP timeout for the monitoring probe. |
ClusterReplicaThreshold | 3 | Minimum cluster size to use NumReplicas=3 for the JetStream streams the SDK provisions. |
Events:ServiceName | derived | Service name embedded in the CloudEvents source (/providers/{ServiceName} by convention). |
Nats:Events is consumed by BeaconTower.Events.AddNatsCloudEventPublisher; additional fields (e.g. JetStream stream name, retention) are documented there.
Telemetry pipeline
{
"TelemetryPipeline": {
"ChannelCapacity": 50000,
"BatchSize": 256,
"FlushIntervalMs": 50,
"MaxReportedConcurrency": 16
}
}
See Telemetry Pipeline › Configuration.
Bus API
The BusApi section is split across two concerns: the keyed connection
(consumed by AddBeaconTowerBusApi from BeaconTower.BusApi) and the
handler tunables (consumed by the SDK's BusApiConsumers background
service). They share a configuration root for ergonomic reasons.
Keyed connection (AddBeaconTowerBusApi)
{
"BusApi": {
"NatsUrl": "nats://bus.internal:4222",
"User": "<bus-user>",
"Password": "<from secret store>",
"TlsCertFile": "",
"TlsKeyFile": ""
}
}
| Option | Default | Description |
|---|---|---|
NatsUrl | required | Canonical-wire NATS URL. Drives the keyed INatsConnection ("btbus") used by the provider control-plane RPC and NatsTemplateClient. |
User / Password | empty | Optional bus authentication — source from a secret store, never from appsettings.json checked into source control. |
TlsCertFile / TlsKeyFile | empty | Optional client TLS material. Required in production deployments where the bus enforces mTLS. |
Wire AddBeaconTowerBusApi before AddBeaconTowerProvider in
Program.cs — the SDK resolves the keyed INatsConnection as a dependency
during provider registration:
builder.Services.AddBeaconTowerBusApi(opts =>
{
opts.NatsUrl = builder.Configuration["BusApi:NatsUrl"];
opts.User = builder.Configuration["BusApi:User"];
opts.Password = builder.Configuration["BusApi:Password"];
});
builder.Services.AddBeaconTowerProvider<MyAdapter>(builder, options =>
{
options.ProviderId = builder.Configuration["Provider:Id"];
});
Nats:Url (data plane — telemetry + JetStream events) and BusApi:NatsUrl
(canonical wire — control plane) are independently configurable. In single-node
development they typically point at the same NATS instance; production
deployments commonly separate them.
Handler tunables (BusApiConsumers)
{
"BusApi": {
"MaxConcurrentHandlers": 50,
"MaxMethodTimeoutSeconds": 300,
"SubscriptionShutdownTimeoutSeconds": 5,
"HandlerDrainTimeoutSeconds": 10
}
}
| Option | Default | Description |
|---|---|---|
MaxConcurrentHandlers | 50 | Semaphore bound on concurrent NATS request handlers (per category). |
MaxMethodTimeoutSeconds | 300 | Server-side cap on cloud-to-device method-invocation timeout. |
SubscriptionShutdownTimeoutSeconds | 5 | Timeout for subscription loops to exit during shutdown. |
HandlerDrainTimeoutSeconds | 10 | Timeout for in-flight handlers to complete during shutdown. |
Credentials
{
"Credentials": {
"MasterKey": "<base64-encoded-32-byte-key>",
"PreviousMasterKeys": [],
"BcryptWorkFactor": 10
}
}
| Option | Default | Description |
|---|---|---|
MasterKey | required | Base64-encoded 256-bit AES key. Encrypts device credentials at rest with AES-256-GCM. |
PreviousMasterKeys | [] | Older keys, oldest → newest. Decrypt-side fallback for rotation; index 0 = version 1, etc. |
BcryptWorkFactor | 10 | Bcrypt cost factor for broker-side password hashing. |
See Credentials for the rotation runbook.
Webhook (CloudEvents transport)
{
"Webhook": {
"TimeoutSeconds": 30,
"Targets": [
{
"Id": "primary",
"Url": "https://example.com/webhooks/beacontower",
"Secret": "<hmac-shared-secret>"
}
]
}
}
| Option | Default | Description |
|---|---|---|
TimeoutSeconds | 30 | Per-request HTTP timeout for the outbound webhook publisher. |
Targets[].Id | required | Routing-target identifier; surfaces on per-target metrics tags. |
Targets[].Url | required | Endpoint that receives the CloudEvents JSON envelope. |
Targets[].Secret | required | HMAC-SHA256 shared secret used for X-Webhook-Signature over the raw body. An empty secret is a configuration error. |
An empty Targets list disables the webhook transport — NATS JetStream still publishes.
Diagnostics
{
"Diagnostics": {
"Enabled": true,
"ChannelCapacity": 100000,
"StatusFlushIntervalSeconds": 5,
"RetentionDays": 30,
"LogBatchSize": 100,
"LogBatchIntervalMs": 1000,
"StatusDrainBatchSize": 256,
"StatusUpsertConcurrency": 16,
"FlapThreshold": 5,
"FlapWindowSeconds": 300,
"FlapCooldownSeconds": 600,
"SummaryMaxStalenessSeconds": 30,
"FilterPollIntervalSeconds": 30,
"PurgeIntervalHours": 24
}
}
| Option | Default | Description |
|---|---|---|
Enabled | true | Master toggle. When false, DiagnosticsEnabledMiddleware short-circuits all /api/diagnostics/* endpoints with HTTP 404. |
ChannelCapacity | 100,000 | Burst-absorption buffer for status/log events. Drops counted via status.drops.total / connection_log.drops.total. |
StatusFlushIntervalSeconds | 5 | Flush cadence for batched LastSeenAt updates. |
RetentionDays | 30 | Connection-log retention before purge. |
LogBatchSize / LogBatchIntervalMs | 100 / 1000 | Connection-log batcher. |
StatusDrainBatchSize | 256 | Per-cycle drain size for DeviceStatusProjection. |
StatusUpsertConcurrency | 16 | Concurrent device-status upserts. |
FlapThreshold / FlapWindowSeconds / FlapCooldownSeconds | 5 / 300 / 600 | Flap detector — 5 disconnects in 5 min flips flapping, with 10 min cooldown. |
SummaryMaxStalenessSeconds | 30 | Cache TTL for the /devices/summary aggregate (spec ceiling). |
FilterPollIntervalSeconds | 30 | Active-log-filter poll interval. |
PurgeIntervalHours | 24 | Log retention sweep cadence. |
Filters are an allow-list — no logs are stored without an active filter.
Provisioning
{
"Provisioning": {
"RetentionDays": 7,
"HardPruneIntervalHours": 24,
"HardPruneHourUtc": 2,
"HardPruneEnabled": true,
"TemplateCacheTtlSeconds": 60,
"TemplateResolveTimeoutSeconds": 5
}
}
| Option | Default | Description |
|---|---|---|
RetentionDays | 7 | Days to retain soft-deleted device rows before hard-deletion. |
HardPruneIntervalHours | 24 | Cadence between hard-prune sweeps after the first. |
HardPruneHourUtc | 2 | UTC hour-of-day of the first daily sweep (0–23). |
HardPruneEnabled | true | When false, HardPruneBackgroundService is not registered. |
TemplateCacheTtlSeconds | 60 | Positive and negative cache TTL for NatsTemplateClient. Lower it only if you accept the extra core-management traffic — the floor matches operator authoring expectations. |
TemplateResolveTimeoutSeconds | 5 | Per-call application timeout for template resolve. Combined with RequestNoResponders=true for fast-fail when no core-management consumer is registered. Must be ≤ the bus-api consumer's overall budget. |
The two template options govern NatsTemplateClient — the SDK's default
ITemplateClient. See Template Resolution for the
wire shape, error-code map, and replacement guidance.
Transformer circuit breaker
The per-device transformer circuit breaker is wired by AddProviderSdk() (called inside AddBeaconTowerProvider). Bind under Transformer to override:
{
"Transformer": {
"FailureThreshold": 10,
"FailureWindow": "00:01:00",
"OpenCircuitTimeout": "00:00:30"
}
}
| Option | Default | Description |
|---|---|---|
FailureThreshold | 10 | Failures within FailureWindow that open the per-device circuit. |
FailureWindow | 60 s | Sliding window over which failures are counted. |
OpenCircuitTimeout | 30 s | Cool-down before retrying a device with an open circuit. |
Authentication (host-level)
JWT Bearer is wired by the host, not the SDK, and follows the canonical pattern in BeaconTower.Core.API (config section AuthenticationOptions:*, OIDC discovery via MetadataAddress, ValidateIssuer = true as the only explicit validator). The reference host validates required production configuration and fails fast on missing values:
| Configuration | Required in Production |
|---|---|
AuthenticationOptions:MetadataAddress | yes (OIDC discovery document URL) |
AuthenticationOptions:Authority | yes (no placeholder values) |
AuthenticationOptions:Audience | yes |
Credentials:MasterKey | yes |
ConnectionStrings:beacontower-mqttproviderdb | yes |
BusApi:NatsUrl | yes (canonical-wire control plane — provider RPC + template resolution) |
Other token-validation parameters (ValidateAudience, ValidateLifetime, ValidateIssuerSigningKey) are left at Microsoft's defaults — all true — so only ValidateIssuer = true is set explicitly.
In Development the reference host swaps in DevBypassAuthHandler, which auto-authenticates with a DeviceAdmin role claim.
Environment variable mapping
All settings can be overridden via environment variables using __ as the separator:
Provider__Id=my-provider
ConnectionStrings__beacontower-mqttproviderdb="Host=..."
Nats__Url=nats://nats:4222
Nats__Events__ServiceName=my-provider
TelemetryPipeline__BatchSize=512
TelemetryPipeline__ChannelCapacity=100000
Credentials__MasterKey=<base64>
Webhook__Targets__0__Id=primary
Webhook__Targets__0__Url=https://example.com/webhooks
Webhook__Targets__0__Secret=<hmac-secret>
Diagnostics__Enabled=true
BusApi__NatsUrl=nats://bus.internal:4222
BusApi__User=<bus-user>
BusApi__Password=<from-secret-store>
BusApi__MaxConcurrentHandlers=64
Provisioning__TemplateCacheTtlSeconds=60
Provisioning__TemplateResolveTimeoutSeconds=5