Real-time event streaming is a core capability of the acre Access Control platform. Every action in the system—from card reads and door status changes to user modifications and system alerts—generates events that are instantly published via MQTT, enabling you to build responsive, data-driven applications.
acre Access Control uses MQTT (Message Queuing Telemetry Transport) as the foundation of our event distribution platform. MQTT is an industry-standard, lightweight publish/subscribe protocol designed for:
Migration Note: acre Access Control migrated from SignalR to MQTT to provide better scalability, cross-platform support, and industry-standard integration patterns.
| Region | API Base URL | MQTT Event Endpoint |
|---|---|---|
| United States | https://api.us.acresecurity.cloud |
wss://events.us.acresecurity.cloud/mqtt |
| Canada | https://api.ca.acresecurity.cloud |
wss://events.ca.acresecurity.cloud/mqtt |
| Europe | https://api.eu.acresecurity.cloud |
wss://events.eu.acresecurity.cloud/mqtt |
Best Practice: Always retrieve the MQTT endpoint dynamically from the API’s SysInfo endpoint rather than hardcoding URLs:
var sysInfo = await client.GetSysInfo();
var mqttEndpoint = sysInfo.EventPublisherUrl; // e.g., "wss://events.us.acresecurity.cloud"
var mqttUrl = $"{mqttEndpoint}/mqtt";
# Using cURL and jq
MQTT_ENDPOINT=$(curl -s https://api.us.acresecurity.cloud/api/sysinfo | jq -r '.EventPublisherUrl')
echo "MQTT Endpoint: ${MQTT_ENDPOINT}/mqtt"
Why dynamic discovery?
MQTT authentication for acre Access Control uses OAuth 2.0 Bearer tokens. Here’s the flow:
/token endpointUnderstanding topic patterns is critical for receiving the right events.
| Topic Pattern | Description | Authorization | Use Case |
|---|---|---|---|
serverclock |
Server time heartbeat (published every second) | Public (no auth required) | Clock synchronization, connection health monitoring |
/{INSTANCE_KEY}/$ |
All events in an instance and its sub-instances | Requires valid token for instance | Dashboards, comprehensive audit logs, real-time monitoring |
/{INSTANCE_KEY}/{OBJECT_KEY} |
Events where a specific object is referenced | Requires valid token for instance | Object-specific monitoring (e.g., all events for a person or reader) |
# Subscribe to all events in instance "69705d500000000000000000"
/69705d500000000000000000/$
# Subscribe to all events for person "678f29d00000000000000000"
/69705d500000000000000000/678f29d00000000000000000
# Subscribe to all events for reader "65aca4d00000000000000000"
/69705d500000000000000000/65aca4d00000000000000000
# Subscribe to server clock (no authentication required)
serverclock
For VAR (Value-Added Reseller) or Enterprise instances:
/{PARENT_INSTANCE_KEY}/$ automatically includes events from all child instancesThis comprehensive example shows production-ready MQTT integration with error handling, automatic reconnection, and event deserialization.
Install the required NuGet packages:
dotnet add package MQTTnet
dotnet add package MQTTnet.Extensions.ManagedClient
dotnet add package MongoDB.Bson
dotnet add package Feenics.Keep.WebApi.Model.Standard
dotnet add package Feenics.Keep.WebApi.Wrapper.Standard
using System;
using System.Threading.Tasks;
using Feenics.Keep.WebApi.Wrapper;
using Feenics.Keep.WebApi.Model;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
using MongoDB.Bson.Serialization;
class EventSubscriber
{
static async Task Main(string[] args)
{
// Configuration
const string baseUrl = "https://api.us.acresecurity.cloud";
const string instance = "QuickStart";
const string username = "admin";
const string password = "your-password";
// 1. Login to API
var client = new Client(baseUrl, userAgent: "EventMonitor/1.0");
var (isLoggedIn, _, _) = await client.LoginAsync(instance, username, password);
if (!isLoggedIn)
{
Console.WriteLine("❌ Login failed");
return;
}
var currentInstance = await client.GetCurrentInstanceAsync();
Console.WriteLine($"✓ Logged in to instance: {currentInstance.CommonName}");
// 2. Get MQTT endpoint dynamically
var sysInfo = await client.GetSysInfo();
var mqttEndpoint = $"{sysInfo.EventPublisherUrl}/mqtt";
Console.WriteLine($"✓ MQTT Endpoint: {mqttEndpoint}");
// 3. Configure MQTT client
var options = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(new MqttClientOptionsBuilder()
.WithCommunicationTimeout(TimeSpan.FromSeconds(5))
.WithKeepAlivePeriod(TimeSpan.FromSeconds(7.5))
.WithCredentials(client.TokenResponse.access_token, "")
.WithWebSocketServer(mqttEndpoint)
.Build())
.Build();
// 4. Create and configure event handlers
var eventsClient = new MqttFactory().CreateManagedMqttClient();
eventsClient.UseConnectedHandler(async e =>
{
Console.WriteLine("✓ MQTT Connected");
// Subscribe to all events in this instance
await eventsClient.SubscribeAsync(new TopicFilterBuilder()
.WithTopic($"/{currentInstance.Key}/$")
.Build());
Console.WriteLine($"✓ Subscribed to: /{currentInstance.Key}/$");
});
eventsClient.UseDisconnectedHandler(e =>
{
Console.WriteLine($"⚠️ MQTT Disconnected. Was connected: {e.ClientWasConnected}");
if (e.Exception != null)
{
Console.WriteLine($" Reason: {e.Exception.Message}");
}
});
eventsClient.UseApplicationMessageReceivedHandler(async e =>
{
try
{
// Deserialize the event message
var message = BsonSerializer.Deserialize<EventMessageData>(e.ApplicationMessage.Payload);
// Display event details
Console.WriteLine($"\n📨 Event Received:");
Console.WriteLine($" Key: {message.Key}");
Console.WriteLine($" Type: {message.EventTypeKey}");
Console.WriteLine($" Time: {message.OccurredOn:yyyy-MM-dd HH:mm:ss} UTC");
Console.WriteLine($" Priority: {message.Priority}");
Console.WriteLine($" Message: {message.MessageLong}");
// Optional: Deserialize event-specific data
if (!string.IsNullOrEmpty(message.EventDataBsonBase64))
{
var eventData = Convert.FromBase64String(message.EventDataBsonBase64);
// Process event-specific data based on EventTypeKey
}
// Display object links (what objects are involved)
if (message.ObjectLinks?.Length > 0)
{
Console.WriteLine($" Objects:");
foreach (var link in message.ObjectLinks)
{
Console.WriteLine($" - {link.Moniker}: {link.CommonName} ({link.Key})");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Error processing message: {ex.Message}");
}
});
// 5. Start MQTT client
await eventsClient.StartAsync(options);
Console.WriteLine("✓ MQTT Client Started");
// 6. Keep running (press Ctrl+C to exit)
Console.WriteLine("\nListening for events... (Press Ctrl+C to exit)\n");
await Task.Delay(Timeout.Infinite);
}
}
✓ Logged in to instance: QuickStart Demo
✓ MQTT Endpoint: wss://events.us.acresecurity.cloud/mqtt
✓ MQTT Connected
✓ Subscribed to: /69705d500000000000000000/$
✓ MQTT Client Started
Listening for events... (Press Ctrl+C to exit)
📨 Event Received:
Key: 637801234567890123
Type: 63cb71500000000000000000
Time: 2026-01-21 18:46:58 UTC
Priority: 200
Message: Access Granted: John Smith at Front Door
Objects:
- Person: John Smith (678f29d00000000000000000)
- Reader: Front Door Reader (65aca4d00000000000000000)
This simpler example shows how to use the public serverclock topic for connection monitoring.
using MQTTnet;
using MQTTnet.Client;
using MongoDB.Bson.Serialization;
class ServerClockMonitor
{
static async Task Main(string[] args)
{
const string mqttEndpoint = "wss://events.us.acresecurity.cloud/mqtt";
var mqttClient = new MqttFactory().CreateMqttClient();
var options = new MqttClientOptionsBuilder()
.WithWebSocketServer(mqttEndpoint)
.Build();
mqttClient.ApplicationMessageReceivedAsync += async e =>
{
var clockData = BsonSerializer.Deserialize<ServerClockMessage>(e.ApplicationMessage.Payload);
Console.WriteLine($"Server Time: {clockData.CurrentTime:HH:mm:ss} UTC");
};
await mqttClient.ConnectAsync(options);
// Subscribe to the public serverclock topic (no authentication required)
await mqttClient.SubscribeAsync("serverclock");
Console.WriteLine("Subscribed to serverclock - receiving heartbeats...\n");
await Task.Delay(Timeout.Infinite);
}
}
// Define the server clock message structure
public class ServerClockMessage
{
public DateTime CurrentTime { get; set; }
}
While cURL isn’t used directly for WebSocket/MQTT connections, you can verify the MQTT endpoint is available and retrieve connection information:
# Get MQTT endpoint from SysInfo
ACCESS_TOKEN="your-access-token"
BASE_URL="https://api.us.acresecurity.cloud"
curl -s "${BASE_URL}/api/sysinfo" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.EventPublisherUrl'
# Output: "wss://events.us.acresecurity.cloud"
For testing MQTT connections, use tools like mosquitto_sub or MQTT Explorer:
# Using mosquitto_sub (install via: brew install mosquitto)
mosquitto_sub -h events.us.acresecurity.cloud \
-p 443 \
-t "/YOUR_INSTANCE_KEY/$" \
-u "$ACCESS_TOKEN" \
-P "" \
--capath /etc/ssl/certs \
-d
Every event message contains a consistent structure:
| Field | Type | Description |
|---|---|---|
Key |
string |
Unique identifier for this event |
EventTypeKey |
string |
Type of event (matches EventTypeInfo.Key) |
OccurredOn |
DateTime |
When the event occurred (UTC) |
Priority |
int |
Event severity (0-1000, lower = more critical) |
MessageShort |
string |
Brief event description |
MessageLong |
string |
Detailed event description |
ObjectLinks |
array |
Objects involved in the event |
EventDataBsonBase64 |
string |
Base64-encoded BSON with event-specific data |
| Event | Description | Priority Range |
|---|---|---|
| Access Granted | Successful card read | 200-300 |
| Access Denied | Failed access attempt | 100-200 |
| Door Forced Open | Door opened without valid credential | 50-100 |
| Door Held Open | Door open beyond timeout | 100-150 |
| Input Activation | Alarm or sensor triggered | 50-200 |
| Person Modified | User record updated | 300-400 |
| System Alert | Controller communication issue | 0-100 |
| Practice | Description |
|---|---|
| Use dynamic endpoint discovery | Always retrieve MQTT URL from SysInfo, don’t hardcode |
| Implement reconnection logic | Use ManagedMqttClient for automatic reconnection |
| Handle token expiration | Tokens expire after 24 hours; implement refresh logic |
| Subscribe efficiently | Use specific object topics when possible instead of /$ wildcard |
| Process events asynchronously | Don’t block the message handler with long operations |
| Log connection status | Track connect/disconnect events for troubleshooting |
| Use QoS appropriately | QoS 1 (at least once) for important events |
| Implement backpressure | Queue events if processing can’t keep up |
| Issue | Possible Cause | Solution |
|---|---|---|
| Connection refused | Invalid endpoint URL | Use SysInfo to get correct endpoint |
| Authentication failed | Expired or invalid token | Refresh access token and reconnect |
| No events received | Wrong topic pattern | Verify instance key and topic format |
| Connection drops | Network instability | Use ManagedMqttClient for auto-reconnect |
| Events delayed | High server load | Check system status; events are eventually consistent |
| Missing events | Topic filter too specific | Use /$ wildcard to receive all events |
| Token rejected | Wrong region | Ensure token was obtained from same region as MQTT endpoint |
TypeScript Example: Check out our example on GitHub: https://github.com/acresecurity/keep-MQTTExample