Skip to content

platform sdk access control raw events

Andre Lafleur edited this page Oct 8, 2026 · 6 revisions

Access control raw events

AccessControlRawEventQuery (ReportType.AccessControlRawEvent) returns the access control events recorded in each Access Manager's event stream (for example AccessGranted, AccessRefused, and unit and door events). These are the same events, with the same EventType values, that the entity-scoped activity queries report; what differs is the retrieval model. Instead of scoping the query to specific doors, areas, or cardholders over a time range, you read each Access Manager's entire event stream incrementally, paging through it by position so repeated calls return only what was recorded since your last call.

Use this query to keep an external system continuously synchronized with access control activity. Unlike DoorActivityQuery and other activity queries, it must be enabled first, filters on insertion time rather than event time, and pages results by position for each Access Manager role.

For the general query lifecycle (CreateReportQuery, BeginQuery/EndQuery, MaximumResultCount, and how a query is dispatched to multiple roles), see About the ReportManager.

Enabling raw event capture

The query returns results only after raw event capture is enabled on the system. Until you enable it, the query completes successfully but returns zero rows, even when access control events exist and other access control queries (such as DoorActivityQuery) return data for the same period.

  1. Open Config Tool.
  2. Go to the System task and select the General settings view.
  3. Open the Advanced settings tab.
  4. Select Add an item.
  5. Set Name to AccessManagerEventStream and Value to True.
  6. Click Apply.

Enter the name exactly as AccessManagerEventStream. The name is case-sensitive, and the value must be True.

Enabling or disabling raw event capture takes effect without restarting the Access Manager role or the server. Capture starts (or stops) as soon as you apply the change.

Important

Only events that occur after you enable this setting are captured. The query cannot return raw events that occurred before the setting was turned on.

This setting controls server-side event capture, so it applies to every SDK client application regardless of the .NET target framework. A client that returns zero raw events is missing this configuration, not a framework-specific difference.

Event time versus insertion time

Each raw event carries two distinct timestamps, and the query treats them differently:

  • Event time (AccessControlRawEventQuery.TableColumns.Timestamp, returned in the EventTimestamp column) is when the event occurred at the hardware.
  • Insertion time (AccessControlRawEventQuery.TableColumns.InsertionTimestamp) is when the Access Manager recorded the event into its stream.

InsertionStartTimeUtc and InsertionEndTimeUtc filter on insertion time, not event time. For an online unit the two timestamps are nearly identical. But when a unit operates offline and buffers its decisions, those events keep their original (earlier) event time and are recorded only when the unit reconnects. Their insertion time can be minutes or hours after their event time, and later than the insertion time of events that happened after them in real time.

Event Event time Insertion time Position
Online access grant 17:03:03 17:03:04 5
Buffered offline grant 17:00:03 17:03:24 8

The offline grant happened earlier (17:00) but was recorded later (17:03:24), so it receives a higher position than the online grant that happened after it.

Important

Because offline events are inserted out of event-time order, you cannot reliably page through this stream by event time. An offline event can be recorded after you have already read past its event time, so a cursor based on EventTimestamp would skip it. Page by position instead.

Paging with the position index

Every event has a Position (AccessControlRawEventQuery.TableColumns.Position): a value the Access Manager assigns in insertion order. Position is the cursor you use to read the stream incrementally.

To request only events recorded after a known point, add a RawEventIndex to StartingAfterIndexes, pairing the AccessManager GUID with the last Position you received. The query returns events strictly after each supplied position; the supplied position itself is excluded. Start from Position = 0 to read from the beginning of the stream. After each batch, take the highest Position you received and use it as the starting position for the next call. The complete example below shows the full loop.

Use position, not a timestamp, as your cursor:

  • Position is unique and strictly increasing, so a "greater than position" cursor never skips or repeats an event, even when multiple events share the same insertion millisecond.
  • Position follows insertion order, so it captures late-arriving offline events that an event-time cursor would miss.

MaximumResultCount (1-50000) caps how many rows a single call returns. When a call returns MaximumResultCount rows, more may be available: advance lastPosition and query again until a call returns fewer rows.

Querying multiple Access Managers

Position is scoped to a single Access Manager. A RawEventIndex therefore pairs a position with the AccessManager GUID it belongs to, and every result row reports its source in AccessControlRawEventQuery.TableColumns.AccessManagerGuid. Position 100 on one Access Manager is unrelated to position 100 on another, so you must track a separate lastPosition cursor per Access Manager.

Each Access Manager maintains its own stream and applies MaximumResultCount to its own results. When a query reaches several Access Managers and their rows together exceed MaximumResultCount, the query completes with ReportError.TooManyResults and can omit rows from Access Managers that have not responded yet.

Warning

If StartingAfterIndexes contains at least one entry, any Access Manager without an entry returns no events. Build your list of Access Managers from the entity cache, not from the AccessManagerGuid values in query results. Otherwise, an Access Manager that returned no rows is excluded from every later query, and no error indicates it.

An Access Manager is missing from the query results when it has no events yet, did not respond, returned a query error, or had its rows omitted because of TooManyResults. Load the roles into the entity cache with a query on EntityType.Role, then enumerate the AccessManagerRole entities, and start each Access Manager that has no saved position at 0. A type-based query also registers the Engine for roles added later. For more information, see About the Engine entity cache.

Query one Access Manager at a time, clearing StartingAfterIndexes and adding the single index for that Access Manager, so each stream is paged with its own cursor:

// Load the roles into the entity cache
var roleQuery = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
roleQuery.EntityTypeFilter.Add(EntityType.Role);
await Task.Factory.FromAsync(roleQuery.BeginQuery, roleQuery.EndQuery, null);

var query = (AccessControlRawEventQuery)engine.ReportManager.CreateReportQuery(ReportType.AccessControlRawEvent);
query.MaximumResultCount = 5000;
query.InsertionStartTimeUtc = DateTime.UtcNow.AddDays(-1);
query.InsertionEndTimeUtc = DateTime.UtcNow;
query.EventTypeFilter.Add(EventType.AccessGranted); // omit to return all event types

foreach (AccessManagerRole accessManager in engine.GetEntities(EntityType.Role).OfType<AccessManagerRole>())
{
    long lastPosition = 0;
    while (true)
    {
        // Resume this Access Manager from its own cursor
        query.StartingAfterIndexes.Clear();
        query.StartingAfterIndexes.Add(new RawEventIndex { AccessManager = accessManager.Guid, Position = lastPosition });

        QueryCompletedEventArgs args = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
        if (args.Data.Rows.Count == 0)
            break;

        foreach (DataRow row in args.Data.Rows)
        {
            long position = row.Field<long>(AccessControlRawEventQuery.TableColumns.Position);
            EventType eventType = row.Field<EventType>(AccessControlRawEventQuery.TableColumns.EventType);
            DateTime eventTime = row.Field<DateTime>(AccessControlRawEventQuery.TableColumns.Timestamp);
            DateTime insertionTime = row.Field<DateTime>(AccessControlRawEventQuery.TableColumns.InsertionTimestamp);
            // process row...
        }

        // Advance the cursor to the highest position in this batch
        lastPosition = args.Data.Rows.Cast<DataRow>().Max(r => r.Field<long>(AccessControlRawEventQuery.TableColumns.Position));

        if (args.Data.Rows.Count < query.MaximumResultCount)
            break;
    }
}

See also

Platform SDK

Plugin SDK

Workspace SDK

Media SDK

Macro SDK

Web SDK

Synergis RIO

Media Gateway

Genetec Web Player

Clone this wiki locally