Repository navigation
platform sdk 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.
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.
- Open Config Tool.
- Go to the System task and select the General settings view.
- Open the Advanced settings tab.
- Select Add an item.
- Set Name to
AccessManagerEventStreamand Value toTrue. - 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.
Each raw event carries two distinct timestamps, and the query treats them differently:
-
Event time (
AccessControlRawEventQuery.TableColumns.Timestamp, returned in theEventTimestampcolumn) 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.
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.
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;
}
}- About the ReportManager: The general query lifecycle, execution patterns, and multi-role dispatch.
- Report Manager query reference: The full catalog of query types, filters, and result schemas.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- ASP.NET hosting
- SDK compatibility
- Migrating to modern .NET
- Entities
- Entity cache
- Entity loading, cache, and query options
- Detecting entity changes
- Entity change event reference
- Transactions
- Security Center events
- Actions
- Security Desk
- Custom events
- Event-to-actions
- Managing macros
- ReportManager
- ReportManager query reference
- Access control raw events
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Entity write restrictions
- About custom fields
- About video
- About cameras
- Enrolling a video unit
- Ingesting third-party video
- Archiver and auxiliary archiver roles
- Archive transfer
- About access control
- About cardholders and credentials
- About doors, areas, elevators, and access points
- About access rules and schedules
- About access control units and interface modules
- Enrolling an access control unit
- Door templates
- Visitors
- Mobile credentials
- About threat levels
- About alarms
- About incidents
- Maps
- Logging
- Overview
- Localization
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Engine extensions
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- Modern .NET support
- Overview
- Localization
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Custom actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- Media file formats
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Endpoints
- Constants
- Enumerations
- Under the hood
- Troubleshooting