Subscribing To Events

Overview

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.

Why MQTT for Access Control?

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:

  • Real-time delivery - Events arrive as they happen
  • Efficiency - Minimal bandwidth usage, ideal for high-volume access control systems
  • Reliability - Built-in Quality of Service (QoS) levels ensure message delivery
  • Scalability - Handle thousands of simultaneous event subscribers without degradation
  • Firewall-friendly - WebSocket transport (WSS) works through standard HTTPS ports

Migration Note: acre Access Control migrated from SignalR to MQTT to provide better scalability, cross-platform support, and industry-standard integration patterns.

Endpoints

Production Endpoints

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

Dynamic Endpoint Discovery

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?

  • Handles routing automatically
  • Accounts for failover scenarios
  • Future-proofs against endpoint changes

Authentication & Authorization

How MQTT Authentication Works

MQTT authentication for acre Access Control uses OAuth 2.0 Bearer tokens. Here’s the flow:

  1. Obtain an access token via the REST API /token endpoint
  2. Connect to MQTT using the access token as your username (password can be empty)
  3. Subscribe to topics - The server validates your token and instance permissions
  4. Receive events - Only events you’re authorized to see are delivered

Security Model

  • Instance-scoped: Your access token determines which instance(s) you can access
  • Topic-level authorization: The server checks every subscription request against your token
  • Automatic expiration: When your token expires (24 hours), you must reconnect with a fresh token
  • TLS encryption: All connections use WSS (WebSocket Secure) for encryption in transit

Topic Structure & Subscription Patterns

Understanding topic patterns is critical for receiving the right events.

Available Topics

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)

Topic Examples

# 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

Multi-Tenant & Enterprise Scenarios

For VAR (Value-Added Reseller) or Enterprise instances:

  • Subscribing to /{PARENT_INSTANCE_KEY}/$ automatically includes events from all child instances
  • Shared instances publish events to both their own topic and parent topics
  • This enables centralized monitoring across customer hierarchies

Complete Implementation Examples

C# Example: Subscribe to All Instance Events

This comprehensive example shows production-ready MQTT integration with error handling, automatic reconnection, and event deserialization.

Prerequisites

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

Full Working Example

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);
    }
}

Expected Output

✓ 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)

C# Example: Monitor Server Clock (Heartbeat)

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; }
}

cURL Example: Test MQTT Connection

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

Event Message Structure

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

Common Event Types

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

Best Practices

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

Troubleshooting

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

Additional Resources

TypeScript Example: Check out our example on GitHub: https://github.com/acresecurity/keep-MQTTExample