Repository navigation
synergis rio developer guide
Synergis RIO (Readers, Inputs, and Outputs) is an HTTPS/XML protocol that lets third-party access control hardware connect to a Synergis appliance. The hardware reports reader activity, input changes, connection state, and optional offline decisions. Synergis Softwire remains the access control engine. It evaluates access rules, schedules, door state, interlocks, antipassback, zones, elevator logic, and the other access control features configured in Security Center.
Use this guide when you are building a RIO device, a RIO simulator, or a gateway that presents third-party hardware to a Synergis appliance.
For a broader comparison between RIO and access control plugins, see Access control integration architecture.
A RIO integration is not a Security Center plugin. It is a hardware-level integration with Synergis Softwire.
Your RIO implementation is responsible for:
- Declaring the hardware topology that exists on the RIO bus
- Reporting whether interfaces and devices are connected
- Reporting input state changes
- Reporting card reads and PIN entries
- Receiving output commands from Softwire and applying them to relays or local hardware
- Receiving reader feedback commands from Softwire and applying them to LEDs, buzzers, displays, or equivalent hardware
- Reporting decisions that were made offline, if your hardware supports an offline operating mode
Softwire is responsible for:
- Creating and exposing the hardware endpoints to Security Center
- Mapping readers, inputs, and outputs to doors, elevators, zones, and other access control entities
- Evaluating access rules and schedules
- Making online grant or deny decisions
- Producing access control events and alarms
- Sending output and reader feedback commands back to the RIO device
RIO integrates third-party hardware with Synergis. It does not integrate an independent access control system that makes its own access decisions.
flowchart LR
subgraph Hardware["Third-party hardware or gateway"]
R1["Readers"]
I1["Inputs"]
O1["Outputs"]
Runtime["RIO runtime"]
end
subgraph Appliance["Synergis appliance"]
Api["ExternalIntegrations API"]
Softwire["Synergis Softwire"]
Bus["RIO bus configuration"]
end
subgraph SecurityCenter["Security Center"]
AM["Access Manager"]
CT["Config Tool"]
SD["Security Desk"]
end
R1 --> Runtime
I1 --> Runtime
Runtime --> O1
Runtime <-->|"HTTPS XML"| Api
Api --> Softwire
Bus --> Softwire
Softwire <-->|"Access control data and events"| AM
CT --> AM
SD --> AM
Your integration communicates with the Synergis appliance over HTTPS.
RIO development requires a Synergis appliance running Synergis Softwire, reachable over HTTPS on TCP 443. Enroll the appliance in Security Center under an Access Manager role as a unit of type Synergis; for the procedure, find Enrolling Synergis units in Security Center for your version on the Genetec TechDoc Hub.
Prepare the appliance for RIO development:
- Create a dedicated user for the RIO runtime in the Synergis Appliance Portal, on the account management page at
https://UNIT-ADDRESS/smc/accountmanagement.html, logged on as an administrator. The password is case sensitive, and the username you list in the channel'sAllowUsersfield must match the account name exactly, including case. - Use an administrator account to create, modify, or delete channels. The runtime user cannot manage channels; it can only send updates and poll commands for the channels that list it in
AllowUsers.
The unit's Hardware page in Config Tool does not display the RIO configuration tab by default. To display it, request https://UNIT-ADDRESS/Features/DuiRIO/Enabled/Set?value=true while logged on to the appliance as an administrator, for example by opening the URL in a browser. GET https://UNIT-ADDRESS/Features/DuiRIO/Enabled returns the current value, and value=false disables the tab after the next restart.
RIO uses XML payloads over HTTPS. The API is exposed under the following base path on the Synergis appliance:
/ExternalIntegrations
The main resources are:
| Operation | Method and path | Purpose |
|---|---|---|
| List channels | GET /ExternalIntegrations/ |
Lists configured external integration channels. |
| Read a channel | GET /ExternalIntegrations/{channel-name} |
Returns the current RIO bus configuration for one channel. |
| Create or replace a channel | PUT /ExternalIntegrations/{channel-name} |
Creates a RIO bus or replaces the full configuration of an existing RIO bus. |
| Delete a channel | DELETE /ExternalIntegrations/{channel-name} |
Removes the RIO bus configuration and the corresponding hardware endpoints. |
| Discover channel protocol | GET /ExternalIntegrations/{channel-name}/Protocol |
Returns the protocol endpoints available for the channel. |
| View update sample | GET /ExternalIntegrations/{channel-name}/Update |
Returns a sample update payload with an HTTP 405 response. This is useful during development. |
| Send runtime updates | POST /ExternalIntegrations/{channel-name}/Update |
Sends keepalive, connectivity, input, card, PIN, or offline-decision updates to Softwire. |
| Start command polling | GET /ExternalIntegrations/{channel-name}/Events/StartSession |
Starts a long-polling session for output and reader commands. |
| Poll commands | GET {LongPollUri}?reqid={n} |
Retrieves downstream commands for the active session. |
| End command polling | GET {HangupUri} |
Ends the long-polling session. |
Send this request over HTTPS to the Synergis appliance. Replace USERNAME and PASSWORD with the appliance account credentials. URL-encode each value before inserting it into the request body.
POST /Login
Content-Type: application/x-www-form-urlencoded
username=USERNAME&password=PASSWORDA successful logon returns HTTP 200 and a .AUTHENTICATION cookie in the Set-Cookie response header. Configure your HTTP client to save this cookie and send it in the Cookie header of subsequent RIO requests.
Use an administrator account for channel configuration. Use the non-admin account listed in AllowUsers to send hardware updates and receive commands.
Production implementations must use TLS 1.2 or later.
There are two distinct authorization paths.
Channel administration:
- Creating, replacing, reading, and deleting a RIO channel is an administrative operation.
- Do this from a setup tool or installer, not from the normal runtime loop if you can avoid it.
- Treat
PUT /ExternalIntegrations/{channel-name}as a full replace operation.
Runtime access:
- The RIO runtime must authenticate to the Synergis appliance.
- Add your integration's non-admin account to the channel's
AllowUsersfield so it can send hardware updates and receive commands. - A dedicated RIO runtime user can be restricted to the RIO channels it needs. It does not need broad Security Center administrative privileges.
- Being allowed on one RIO channel does not grant access to other RIO channels.
Recommended practice:
- Use one dedicated runtime account per product, site, or device fleet.
- Put that account in
AllowUsers. - Keep channel administration separate from runtime operation.
- Reauthenticate cleanly if the appliance returns a redirect or authentication failure.
A RIO channel describes a bus of one or more third-party interfaces. Each interface contains readers, inputs, and outputs. Device IDs are generated by position.
| Configuration element | Meaning |
|---|---|
RioBus |
The RIO bus configuration for the channel. |
Interfaces |
The list of interfaces exposed by the RIO bus. |
CustomInterface |
A single third-party interface on the bus. |
Address |
The stable interface address used by runtime messages and hardware paths. |
Template |
Manufacturer, model, description, and labels for readers, inputs, and outputs. |
InputLabels |
Labels used to create inputs. They become I1, I2, I3, and so on. |
OutputLabels |
Labels used to create outputs. They become O1, O2, O3, and so on. |
ReaderLabels |
Labels used to create readers. They become R1, R2, R3, and so on. |
ReportAsSimulated |
Optional. Reports the interface as simulated hardware. Use this for simulators and test tools, not production hardware. |
AllowUsers |
Optional runtime access list. Add the RIO runtime username here. |
Device IDs are not copied from the label text. They are positional:
| Label list | Generated device IDs |
|---|---|
ReaderLabels |
R1, R2, R3, ... |
InputLabels |
I1, I2, I3, ... |
OutputLabels |
O1, O2, O3, ... |
Use stable channel names, interface addresses, and label order. Changing them can create a different hardware identity from the point of view of Softwire and Security Center.
RIO devices have stable hardware paths.
/Devices/Bus/External/{channel-name}/{interface-address}/Reader/{reader-id}
/Devices/Bus/External/{channel-name}/{interface-address}/Input/{input-id}
/Devices/Bus/External/{channel-name}/{interface-address}/Output/{output-id}
Examples:
/Devices/Bus/External/DemoCampus/MainEntrance/Reader/R1
/Devices/Bus/External/DemoCampus/MainEntrance/Input/I1
/Devices/Bus/External/DemoCampus/MainEntrance/Output/O1
In Config Tool, the hardware is displayed using the manufacturer, model, channel name, and interface address. For example:
Acme Controls - ACME-2DR (DemoCampus - MainEntrance)
Create or replace a channel with PUT /ExternalIntegrations/{channel-name}.
The request body is a Request wrapper that contains a RioBus.
In the examples, REX stands for request to exit.
<Request>
<RioBus>
<Interfaces>
<Interface>
<CustomInterface>
<Address>MainEntrance</Address>
<Template>
<Manufacturer>Acme Controls</Manufacturer>
<ModelName>ACME-2DR</ModelName>
<Description>Two door RIO interface</Description>
<InputLabels>
<String>Door contact 1</String>
<String>REX 1</String>
<String>Door contact 2</String>
<String>REX 2</String>
</InputLabels>
<OutputLabels>
<String>Lock relay 1</String>
<String>Lock relay 2</String>
</OutputLabels>
<ReaderLabels>
<String>Reader 1</String>
<String>Reader 2</String>
</ReaderLabels>
</Template>
</CustomInterface>
</Interface>
</Interfaces>
<ReportAsSimulated>false</ReportAsSimulated>
<AllowUsers>
<String>rio-runtime</String>
</AllowUsers>
</RioBus>
</Request>The response contains the channel's API path:
/ExternalIntegrations/DemoCampus/
After PUT returns successfully, poll GET /ExternalIntegrations/{channel-name} until the channel can be read before sending discovery requests or runtime updates. A newly created channel can briefly return 404 Integration not found while Softwire finishes registering it.
To define multiple interfaces on the same channel, add more Interface entries. Each Interface entry contains one CustomInterface.
<Request>
<RioBus>
<Interfaces>
<Interface>
<CustomInterface>
<Address>MainEntrance</Address>
<Template>
<Manufacturer>Acme Controls</Manufacturer>
<ModelName>ACME-2DR</ModelName>
<Description>Main entrance controller</Description>
<InputLabels>
<String>Door contact</String>
<String>REX</String>
</InputLabels>
<OutputLabels>
<String>Lock relay</String>
</OutputLabels>
<ReaderLabels>
<String>Entry reader</String>
</ReaderLabels>
</Template>
</CustomInterface>
</Interface>
<Interface>
<CustomInterface>
<Address>ShippingDoor</Address>
<Template>
<Manufacturer>Acme Controls</Manufacturer>
<ModelName>ACME-1DR</ModelName>
<Description>Shipping door controller</Description>
<InputLabels>
<String>Door contact</String>
<String>REX</String>
</InputLabels>
<OutputLabels>
<String>Lock relay</String>
</OutputLabels>
<ReaderLabels>
<String>Entry reader</String>
</ReaderLabels>
</Template>
</CustomInterface>
</Interface>
</Interfaces>
<AllowUsers>
<String>rio-runtime</String>
</AllowUsers>
</RioBus>
</Request>Read a channel:
GET /ExternalIntegrations/DemoCampusList channels:
GET /ExternalIntegrations/Delete a channel:
DELETE /ExternalIntegrations/DemoCampusA delete removes the RIO bus and the corresponding hardware endpoints. Do not delete and recreate a channel as a normal update strategy. If you need to modify the bus, read the current configuration, preserve stable names and label order, and then send a replacement RioBus with PUT.
Use an administrator account to check that the RIO channel exists. The non-admin account cannot read the channel configuration.
Add the non-admin account to the channel's AllowUsers field. Your integration uses this account to send hardware updates and receive commands.
A production RIO runtime follows this lifecycle:
- Authenticate to the Synergis appliance.
- Start the keepalive watchdog by posting
StatusKeepalive. - Mark the interface or devices online by posting
SetConnected. - Start a long-polling session with
Events/StartSession. - Poll for downstream commands using the returned
LongPollUri. - Send card, PIN, input, and connectivity updates as hardware activity occurs.
- Refresh the keepalive before the previous duration expires.
- Apply output and reader feedback commands idempotently.
- On clean shutdown, mark affected interfaces or devices offline and call the
HangupUri.
Keepalive and connected state are separate:
-
StatusKeepalivestarts or refreshes the watchdog and sets its duration. -
SetConnectedreports whether interfaces or devices are online or offline. - If the keepalive expires, Softwire marks the RIO interfaces offline and sends a downstream
KeepaliveTimeoutmessage. - A later
StatusKeepalivereplaces the previous watchdog duration.
Use a keepalive duration that is longer than the send interval. A common pattern is to send a keepalive every 5 seconds with a 10-second duration. Slower devices can use a longer interval, but set the duration to about twice the send interval.
Runtime updates are sent with POST /ExternalIntegrations/{channel-name}/Update.
The body is a Request wrapper that contains one or more BusUpdate elements.
<Request>
<BusUpdate>
<StatusKeepalive>
<Duration>00:00:10</Duration>
</StatusKeepalive>
</BusUpdate>
<BusUpdate>
<SetConnected>
<Interface>MainEntrance</Interface>
<IsConnected>true</IsConnected>
<SpecificDevices>
<None />
</SpecificDevices>
</SetConnected>
</BusUpdate>
</Request>Batching is supported, but use it carefully. Softwire validates each update and returns errors for invalid items. Do not mix critical updates with experimental or optional updates in the same request, because a partially valid batch can still have side effects before the error response is returned.
You can request a sample update payload during development:
GET /ExternalIntegrations/DemoCampus/UpdateThe expected response status is HTTP 405, because updates must be sent with POST. The response body is still useful because it shows the expected XML structure.
StatusKeepalive starts or refreshes the RIO watchdog.
<Request>
<BusUpdate>
<StatusKeepalive>
<Duration>00:00:10</Duration>
</StatusKeepalive>
</BusUpdate>
</Request>| Field | Required | Notes |
|---|---|---|
Duration |
Yes | Watchdog duration in .NET TimeSpan format, such as 00:00:10. ISO 8601 values such as PT10S are rejected. |
Implementation guidance:
- Send
StatusKeepalivebeforeSetConnectedwhen the runtime starts. - Refresh it periodically before it expires.
- If the runtime loses communication with the appliance, reconnect and send a new keepalive before resuming normal event traffic.
- Treat
KeepaliveTimeoutfrom the downstream poll as a signal that Softwire marked the channel offline.
SetConnected reports whether a full interface or selected devices are online.
Bring a full interface online:
<Request>
<BusUpdate>
<SetConnected>
<Interface>MainEntrance</Interface>
<IsConnected>true</IsConnected>
<SpecificDevices>
<None />
</SpecificDevices>
</SetConnected>
</BusUpdate>
</Request>Bring a full interface offline:
<Request>
<BusUpdate>
<SetConnected>
<Interface>MainEntrance</Interface>
<IsConnected>false</IsConnected>
<SpecificDevices>
<None />
</SpecificDevices>
</SetConnected>
</BusUpdate>
</Request>Report specific devices online:
<Request>
<BusUpdate>
<SetConnected>
<Interface>MainEntrance</Interface>
<IsConnected>true</IsConnected>
<SpecificDevices>
<String>R1</String>
<String>I1</String>
<String>O1</String>
</SpecificDevices>
</SetConnected>
</BusUpdate>
</Request>Report specific devices offline:
<Request>
<BusUpdate>
<SetConnected>
<Interface>MainEntrance</Interface>
<IsConnected>false</IsConnected>
<SpecificDevices>
<String>R2</String>
</SpecificDevices>
</SetConnected>
</BusUpdate>
</Request>| Field | Required | Notes |
|---|---|---|
Interface |
Yes | Interface address from the channel configuration. |
IsConnected |
Yes |
true for online, false for offline. |
SpecificDevices |
Yes | Use <None /> for the full interface, or include device IDs directly for selected readers, inputs, and outputs. |
Behavior details:
- With
<None />, Softwire applies the state to the interface and all devices on the interface. - With specific device IDs and
IsConnected=true, Softwire marks the interface online and then marks the listed devices online. - With specific device IDs and
IsConnected=false, Softwire marks only the listed devices offline. - Use specific devices for partial failures, such as one reader disconnected while the controller remains online.
InputStatusUpdate reports a state change for one input.
<Request>
<BusUpdate>
<InputStatusUpdate>
<Interface>MainEntrance</Interface>
<Input>I1</Input>
<State>
<Active />
</State>
<Timestamp>2026-07-08T14:25:10Z</Timestamp>
</InputStatusUpdate>
</BusUpdate>
</Request>| Field | Required | Notes |
|---|---|---|
Interface |
Yes | Interface address from the channel configuration. |
Input |
Yes | Input ID such as I1. |
State |
Yes | One of <Normal />, <Active />, <Trouble />, <Cut />, or <Short /> inside the State element. |
Timestamp |
No | Softwire ignores this field for input state updates. |
Input states:
| RIO state | Meaning in Softwire |
|---|---|
Normal |
Input is in normal or inactive state. |
Active |
Input is active. |
Trouble |
Input trouble state, unknown trouble type. |
Cut |
Input trouble state, cut. |
Short |
Input trouble state, short. |
Use Normal and Active for normal input transitions. Use Trouble, Cut, and Short only when the hardware can detect those conditions. Do not serialize the state as text, such as <State>Normal</State>.
CardSwipe reports a credential read from a reader.
<Request>
<BusUpdate>
<CardSwipe>
<Interface>MainEntrance</Interface>
<Reader>R1</Reader>
<BitCount>26</BitCount>
<CardCode>026E0D06</CardCode>
<Timestamp>2026-07-08T14:26:00Z</Timestamp>
</CardSwipe>
</BusUpdate>
</Request>| Field | Required | Notes |
|---|---|---|
Interface |
Yes | Interface address from the channel configuration. |
Reader |
Yes | Reader ID such as R1. |
BitCount |
Yes | Number of meaningful credential bits. |
CardCode |
Yes | Hexadecimal card data. The value must contain full bytes. |
Timestamp |
No | UTC timestamp. If omitted, Softwire uses the appliance time. |
Credential encoding rules:
-
CardCodeis hexadecimal byte data. -
BitCountspecifies the number of meaningful bits inCardCode. - A card code can be up to 512 bits.
- For an N-byte
CardCode, validBitCountvalues are fromN * 8 - 7throughN * 8. - Example: a 4-byte
CardCodeallowsBitCountvalues from 25 through 32. - A 26-bit credential is therefore encoded in 4 bytes, for example
026E0D06withBitCountset to26.
After Softwire receives a card swipe, it evaluates the access decision. If access is granted, Softwire can send reader feedback and output commands through the long-polling channel. If access is denied, Softwire can send denied reader feedback.
CardCode contains the credential's raw card data with the meaningful bits right-aligned: pad the leading end with zero bits to fill full bytes. Set every pad bit to zero; a read whose pad bits are not zero does not match any credential.
In the standard 26-bit Wiegand format, the bits are, in order: one even parity bit computed over the first 12 data bits, the 8-bit facility code, the 16-bit card number, and one odd parity bit computed over the last 12 data bits.
To derive the value for facility code 55, card number 1667:
- Write the data bits: facility code 55 is
00110111, and card number 1667 is0000011010000011. - Compute the parity bits. The first 12 data bits
001101110000contain five 1 bits, so the even parity bit is 1. The last 12 data bits011010000011contain five 1 bits, so the odd parity bit is 0. - Assemble the 26 bits,
1 00110111 0000011010000011 0, and pad with six leading zero bits to fill 4 bytes. - The result is
CardCode026E0D06withBitCount26.
For a custom card format, apply the same procedure using the field masks and parity checks of the format definition; see custom card formats. To create credentials that match your test reads, see About cardholders and credentials.
A license plate credential is matched when the plate text is reported as a CardSwipe: encode each character as two bytes, the character's ASCII value followed by 00, and set BitCount to 16 times the number of characters. For the plate FET2605, CardCode is 4600450054003200360030003500 and BitCount is 112.
- The match is exact on the plate text stored in the credential; unlike an ALPR reader, no misread tolerance is applied.
- The resulting access events are not attributed to the credential entity, so reports such as Door activities do not identify the credential.
PinEntry reports a PIN entered at a reader.
<Request>
<BusUpdate>
<PinEntry>
<Interface>MainEntrance</Interface>
<Reader>R1</Reader>
<Pin>1234</Pin>
<Timestamp>2026-07-08T14:27:00Z</Timestamp>
</PinEntry>
</BusUpdate>
</Request>| Field | Required | Notes |
|---|---|---|
Interface |
Yes | Interface address from the channel configuration. |
Reader |
Yes | Reader ID such as R1. |
Pin |
Yes | Digits only. |
Timestamp |
No | UTC timestamp. If omitted, Softwire uses the appliance time. |
PIN validation:
- The PIN can contain digits only.
- The maximum length is controlled by the appliance access control settings.
- If the hardware sends keypad digits as individual 4-bit reader values, report them as card swipes only if that is the expected reader behavior for the configured reader.
- For normal card-and-PIN operation, use
CardSwipefor the card read andPinEntryfor the PIN.
OfflineDecision reports an access decision that the hardware made while operating offline.
Use this only when the hardware made the decision locally because it was unable to obtain an online decision from Softwire. Do not use OfflineDecision during normal online operation.
Granted offline decision with a card:
<Request>
<BusUpdate>
<OfflineDecision>
<Interface>MainEntrance</Interface>
<Reader>R1</Reader>
<Timestamp>2026-07-08T14:28:00Z</Timestamp>
<Card>
<Some>
<BitCount>26</BitCount>
<CardCode>026E0D06</CardCode>
</Some>
</Card>
<Granted>true</Granted>
</OfflineDecision>
</BusUpdate>
</Request>Denied offline decision with a PIN:
<Request>
<BusUpdate>
<OfflineDecision>
<Interface>MainEntrance</Interface>
<Reader>R1</Reader>
<Timestamp>2026-07-08T14:28:30Z</Timestamp>
<Card>
<None />
</Card>
<Pin>1234</Pin>
<Granted>false</Granted>
</OfflineDecision>
</BusUpdate>
</Request>| Field | Required | Notes |
|---|---|---|
Interface |
Yes | Interface address from the channel configuration. |
Reader |
Yes | Reader ID such as R1. |
Timestamp |
Yes | UTC timestamp for the offline decision. |
Card |
No | Optional card credential. Use <Card><None /></Card> when you need to explicitly report no card. |
Pin |
No | Optional PIN. If present, use the PIN digits as the element text. |
Granted |
Yes |
true if the local hardware granted access, false if it denied access. |
Behavior details:
- If a card is included, the same card encoding rules as
CardSwipeapply. - If a PIN is included, the same PIN validation rules as
PinEntryapply. - Softwire records an autonomous access decision event.
- If the offline decision is granted and Softwire can associate the credential with a cardholder, Softwire can also create the corresponding movement event.
RIO uses long polling for commands from Softwire to the runtime. Long polling is a normal HTTP GET that Softwire holds open until one of these conditions occurs:
- A command or event is available for the runtime.
- The maximum response timeout expires.
- The session is ended.
- The request violates the session rules.
Long polling is not a WebSocket and it is not a background callback from Softwire to your device. Your runtime always initiates the request.
Start a long-polling session with this request:
GET /ExternalIntegrations/DemoCampus/Events/StartSessionThe response contains the values your runtime needs for the polling loop.
| Field | Meaning |
|---|---|
LongPollUri |
URI to call for command polling. This value does not include reqid. |
HangupUri |
URI to call when the session is no longer needed. |
InitialState |
Current bus state, including the appliance time and known output states. |
MaxResponseTimeout |
Maximum time the server can hold a poll request before returning an empty response. |
SupportsRetry |
Indicates whether retry behavior is supported for request IDs. |
AdditionalEndpoints |
Extra endpoint metadata, if any. |
A typical response looks like this:
<Response>
<LongPollUri>
/ExternalIntegrations/DemoCampus/Events/LongPoll/1554473948
</LongPollUri>
<HangupUri>
/ExternalIntegrations/DemoCampus/Events/EndSession/1554473948
</HangupUri>
<AdditionalEndpoints />
<InitialState>
<CurrentTime>2026-07-08T09:12:25.419Z</CurrentTime>
<Outputs />
</InitialState>
<MaxResponseTimeout>120</MaxResponseTimeout>
<SupportsRetry>true</SupportsRetry>
</Response>The numeric value at the end of LongPollUri is the server session ID. It is not the request ID.
Your runtime must append a client-generated reqid query parameter to the LongPollUri.
GET /ExternalIntegrations/DemoCampus/Events/LongPoll/1554473948?reqid=1
GET /ExternalIntegrations/DemoCampus/Events/LongPoll/1554473948?reqid=2
GET /ExternalIntegrations/DemoCampus/Events/LongPoll/1554473948?reqid=3Start at reqid=1 for each new session. Increment reqid only after receiving the complete HTTP 200 response and processing its commands. An empty response also counts as success.
When your runtime sends a poll request, Softwire can keep the request open for up to MaxResponseTimeout seconds. A typical value is 120 seconds.
This is expected behavior:
- Your runtime sends
GET {LongPollUri}?reqid=1. - Softwire has no command ready, so it does not respond immediately.
- A command becomes available, or the timeout expires.
- Softwire returns an XML response.
- Your runtime processes the response.
- Your runtime sends the next request with
reqid=2.
Configure your HTTP client read timeout to be longer than MaxResponseTimeout, plus a small network margin. If the client times out first, treat that timeout as an ambiguous transport failure and retry the same reqid.
SupportsRetry=true means Softwire can protect your runtime from losing a response when the client cannot determine whether the previous request completed.
Use this retry rule:
- Send
GET {LongPollUri}?reqid=N. - If you receive a complete HTTP 200 response, process it and increment to
N + 1. Handle other HTTP statuses as errors. - If the connection fails before you receive an HTTP response, retry
reqid=N. - If Softwire already completed that
reqid, it can replay the cached response. - If the retry returns the cached response, process it once and then increment to
N + 1.
Do not use the replay behavior as your normal polling loop. It exists for transport failures and ambiguous delivery.
Send one poll at a time. Wait for its response before sending the next request ID.
If a connection failure requires you to retry, reuse the same reqid. If the original request is still pending, Softwire returns HTTP 200 with an empty response to both requests.
Sending a different reqid, or omitting it, while a poll is pending causes this error:
Received another request for the same session
After this error, close the session using HangupUri if possible. Start a new session and begin polling with reqid=1.
If no commands are available, Softwire holds the request until MaxResponseTimeout expires, then returns an empty response.
<Response />An empty response is not an error. Process it as "no commands available" and immediately issue the next poll with the next reqid.
This pseudocode shows the required control flow.
Apply the output commands in InitialState.Outputs whenever you start a session. If Outputs is empty, there are no initial output commands to apply.
session = start_session()
apply_initial_outputs(session.InitialState.Outputs)
reqid = 1
while runtime_is_running:
try:
response = GET session.LongPollUri + "?reqid=" + reqid
except transport_failure:
continue
if response.status == 200:
process_bus_messages(response.body)
reqid = reqid + 1
continue
if response.body contains "Received another request":
GET session.HangupUri
session = start_session()
apply_initial_outputs(session.InitialState.Outputs)
reqid = 1
continue
handle_error(response)
Here, apply_initial_outputs represents the integration's code for applying those commands.
During clean shutdown, call the HangupUri for the active session.
The long-polling session starts with an initial bus state.
Important fields:
| Field | Meaning |
|---|---|
CurrentTime |
Appliance time when the initial state was generated. |
Outputs |
Latest command state for outputs known to Softwire. |
Outputs can be empty:
<InitialState>
<CurrentTime>2026-07-08T09:12:25.419Z</CurrentTime>
<Outputs />
</InitialState>When output state is available, Outputs contains OutputControlMessage entries. The initial state structure is different from later poll responses, which use BusMessage entries.
<InitialState>
<CurrentTime>2026-07-08T09:12:25.419Z</CurrentTime>
<Outputs>
<OutputControlMessage>
<Interface>MainEntrance</Interface>
<Output>O1</Output>
<StartTime>2026-07-08T09:12:20.000Z</StartTime>
<Command>
<Clear />
</Command>
</OutputControlMessage>
</Outputs>
</InitialState>Use InitialState to reconcile your hardware outputs when a runtime reconnects. If Softwire previously sent a Pulse command and the pulse duration has already expired, the initial output state is reported as Clear.
Softwire sends output commands when it needs your runtime to change an output state.
Example output command:
<Response>
<BusMessage>
<Output>
<Interface>MainEntrance</Interface>
<Output>O1</Output>
<StartTime>2026-07-08T14:26:00Z</StartTime>
<Command>
<Pulse>
<Duration>00:00:05</Duration>
</Pulse>
</Command>
</Output>
</BusMessage>
</Response>| Field | Meaning |
|---|---|
Interface |
Interface address from the RIO channel. |
Output |
Output ID such as O1. |
StartTime |
Appliance time associated with the command. |
Command |
Set, Clear, or Pulse. |
Output command meanings:
| Command | Meaning |
|---|---|
Set |
Set the output to the active state. |
Clear |
Set the output to the normal state. |
Pulse |
Set the output to the active state for the specified duration, then return it to normal. |
Pulse durations use .NET TimeSpan format. Parse values such as 00:00:05 and fractional values such as 00:00:05.5980000.
A pulse ends at StartTime + Duration. If you receive the command after its start time, activate the output only for the remaining time. If that end time has already passed, do not activate the output.
A retry can return the same pulse command with its original StartTime and Duration. Do not restart the pulse or extend its end time when you receive it again.
One access decision or door action can generate more than one downstream message. For example, a granted card read can produce a reader GrantedFeedback message and a separate output Pulse message. These messages can arrive in separate poll responses. Keep polling until you have processed all messages that follow the triggering event.
Door control actions can also generate output commands:
- A momentary unlock action can generate
Pulse. - A free-access or unlock override can generate
Set. - Returning the door to controlled access can generate
Clear.
Do not hard-code Set as unlock and Clear as lock. The physical meaning depends on how the output is wired and how the door is configured. Treat Set, Clear, and Pulse as electrical output states.
Softwire sends reader feedback commands after an access decision.
Granted feedback:
<Response>
<BusMessage>
<Reader>
<Interface>MainEntrance</Interface>
<Reader>R1</Reader>
<StartTime>2026-07-08T14:26:00Z</StartTime>
<Command>
<GrantedFeedback />
</Command>
</Reader>
</BusMessage>
</Response>Denied feedback:
<Response>
<BusMessage>
<Reader>
<Interface>MainEntrance</Interface>
<Reader>R1</Reader>
<StartTime>2026-07-08T14:26:02Z</StartTime>
<Command>
<DeniedFeedback />
</Command>
</Reader>
</BusMessage>
</Response>Reader feedback commands:
| Command | Suggested hardware behavior |
|---|---|
GrantedFeedback |
Show access granted feedback on the reader. |
DeniedFeedback |
Show access denied feedback on the reader. |
The exact LED, buzzer, or display behavior depends on your reader and product requirements.
For a mapped door, a valid card read normally produces GrantedFeedback and an output command for the lock. An unknown or unauthorized card read normally produces DeniedFeedback and no lock output command.
If the keepalive watchdog expires, Softwire marks the RIO interfaces offline and can send a downstream message:
<Response>
<BusMessage>
<KeepaliveTimeout />
</BusMessage>
</Response>When the runtime receives this message:
- Refresh the keepalive after reconnecting.
- Send
SetConnectedfor the current interface or device state. - Reconcile outputs using the current polling state.
- Record the timeout in your device logs.
The typical online sequence is:
- Runtime sends
StatusKeepalive. - Runtime sends
SetConnectedfor the interface. - Runtime starts long polling.
- User presents a card.
- Runtime sends
CardSwipe. - Softwire evaluates the cardholder, credential, access rules, schedules, door state, and related access control logic.
- If access is granted, Softwire sends
GrantedFeedbackand the required output command, often aPulseon the lock relay. - If access is denied, Softwire sends
DeniedFeedback. - Runtime applies commands to the reader and output hardware.
- Runtime continues reporting inputs, card reads, PIN entries, and connectivity changes.
Softwire makes the access decision. Your runtime must not grant online access on its own while Softwire is reachable.
After the RIO channel is created, the hardware endpoints can be assigned to access control entities in Config Tool.
Typical door mapping:
| Door function | RIO device |
|---|---|
| Entry reader | Reader/R1 |
| Exit reader |
Reader/R2, if present |
| Door contact | Input/I1 |
| REX input | Input/I2 |
| Lock output | Output/O1 |
The exact mapping depends on the door design. Once mapped, Softwire uses the RIO devices as part of the normal Synergis access control model. That means the same RIO hardware can participate in supported Synergis features such as doors, elevators, zones, I/O zones, schedules, card and PIN behavior, interlocks, and antipassback, depending on the Security Center configuration and licensed features.
After the channel is created, the Synergis appliance appears as a Unit entity, each RIO interface as an InterfaceModule entity, and each reader, input, and output as a Device entity. For the unit and interface module model, see About access control units and interface modules.
The unit's Devices property returns the GUIDs of its devices, and its InterfaceModules property returns the GUIDs of its interface modules. A device's UniqueId property contains its hardware path, so the path segments identify the channel name, the interface address, and the device ID that BusUpdate messages use. The DeviceType property distinguishes readers, inputs, and outputs, and the InterfaceModule and Unit properties identify the device's interface module and unit. Match devices on the full UniqueId path: a device ID such as R1 repeats on every interface.
void PrintRioDevices(Engine engine, Guid unitGuid, string channelName)
{
var unit = (Unit)engine.GetEntity(unitGuid);
string prefix = $"/Devices/Bus/External/{channelName}/";
foreach (Guid deviceGuid in unit.Devices)
{
var device = (Device)engine.GetEntity(deviceGuid);
if (device.UniqueId != null && device.UniqueId.StartsWith(prefix))
{
Console.WriteLine($"{device.DeviceType}: {device.UniqueId}");
}
}
}For the DemoCampus channel, the output contains lines such as Reader: /Devices/Bus/External/DemoCampus/MainEntrance/Reader/R1.
The Web SDK forms:
GET /report/EntityConfiguration?q=EntityTypes@Unit,PageSize=100,ReturnFields@Name
GET /entity?q=entity={unit-guid},Devices,InterfaceModules
GET /entity?q=entity={device-guid},Name,UniqueId,DeviceTypeThe returned GUIDs are the values that the door wiring calls below expect: SetPreferredInterface takes the InterfaceModule GUID, and UpdateConnection takes the Device GUIDs.
To validate reader feedback and output commands end to end, configure a temporary door that uses the RIO devices.
- Create a RIO channel that has at least one reader, one input for the door sensor, one input for the request to exit, and one output for the lock.
- Discover the generated Security Center entities. RIO readers, inputs, and outputs appear as
Deviceentities. The RIO interface appears as anInterfaceModuleentity. The Synergis appliance appears as aUnitentity. - Create a temporary door, cardholder, credential, and access rule.
- Add the cardholder or cardholder group to the access rule.
- Apply the access rule to the door side that contains the RIO reader.
- Set the door preferred unit, preferred interface, and lock output.
- Read the door side access points from
DoorSideInorDoorSideOut. - Use
UpdateConnectionto connect the RIO reader, request-to-exit input, and door sensor input to the door side access points. - Bring the RIO interface online with
StatusKeepaliveandSetConnected. - Start long polling.
- Trigger a door action or send a
CardSwipe.
The Web SDK wiring calls use this format:
POST /entity?q=entity={door-guid},SetPreferredUnit({unit-guid}),SetPreferredInterface({interface-guid}),DoorLockDevice={lock-output-device-guid}
GET /entity?q=entity={door-guid},DoorSideIn,DoorSideOut
POST /entity?q=entity={door-guid},UpdateConnection({reader-access-point-guid},{reader-device-guid}),UpdateConnection({rex-access-point-guid},{rex-input-device-guid}),UpdateConnection({sensor-access-point-guid},{sensor-input-device-guid})Use UpdateConnection, not AddConnection, for the reader, request-to-exit input, and door sensor access points that already exist on the door side.
Expected validation results:
-
UnlockDoor({door-guid})generates an outputPulse. -
OverrideLockingScheduleOnDoor({door-guid},true,Hour,1)generates outputSet. -
OverrideLockingScheduleOnDoor({door-guid},false,Hour,1)generates outputClear. - A known card with access generates
GrantedFeedbackand an outputPulse. - An unknown or unauthorized card generates
DeniedFeedback.
Handle HTTP status and protocol errors explicitly.
| Condition | Typical result | Correct response |
|---|---|---|
| Authentication expired or failed | Redirect or authentication error | Reauthenticate and retry safely. |
| Account does not have permission for the requested operation | Forbidden | For hardware updates and command polling, add the non-admin account to the channel's AllowUsers field. For channel configuration, use an administrator account. |
| Channel does not exist | Not found | Create the channel or correct the channel name. |
GET /Update |
Method not allowed with sample body | Use the sample body for development, but send real updates with POST. |
| Unknown interface or device | Error response with message | Fix the channel configuration or runtime device ID. |
| Invalid card bit count | Error response with message | Fix CardCode length or BitCount. |
| Invalid PIN | Error response with message | Send digits only and respect the configured maximum length. |
| Concurrent long poll for same session | Error response with message | End the session if possible, start a new session, and ensure only one in-flight poll per session. |
| Expired long-polling session | Gone or session timeout response | Start a new session. |
Common protocol error messages include:
| Message pattern | Likely cause |
|---|---|
Interface {name} not found |
Runtime used an interface address that is not in the channel. |
Reader {id} not found on interface {name} |
Runtime used a reader ID that does not exist on that interface. |
Input {id} not found on interface {name} |
Runtime used an input ID that does not exist on that interface. |
Invalid BitCount. A 4 byte card code must have 25 <= BitCount <= 32 |
Card bit count does not match the number of bytes in CardCode. |
Pin may only include digits |
PIN contained a non-digit character. |
Unknown input status "{value}" |
Input state was not one of the supported values. |
Unknown message type "{value}" |
The BusUpdate contained an unknown message. |
Received another request for the same session |
Long-polling client sent concurrent requests for one session. |
Log enough detail to diagnose problems:
- Channel name
- Interface address
- Device ID
- Update type
- Timestamp sent by the runtime
- HTTP status
- Response message
- Long-polling session ID and
reqid, when applicable
Design within the supported limits of the appliance and Security Center version you target.
| Limit | Value |
|---|---|
| Interfaces per Softwire appliance | 64 |
| Readers per Softwire appliance | 256 |
| Credentials | 600,000 |
| Maximum credential size | 512 bits |
| Long-polling response timeout | Typically 120 seconds |
| Long-polling command batch size | 100 messages |
Implementation guidance:
- Keep names and label order stable between updates.
- Avoid unnecessary channel replacement.
- Refresh keepalive on a fixed timer.
- Debounce noisy inputs before sending state changes.
- Treat output commands as idempotent. Applying the same command twice must not damage state.
- Separate runtime health monitoring from access events in your logs.
- Test with more than one interface per channel if your product supports multiple panels.
Channel setup:
- Choose a stable channel name.
- Choose stable interface addresses.
- Define reader, input, and output labels in the intended order.
- Create a dedicated runtime user.
- Add the runtime user to
AllowUsers. - Create the channel with
PUT /ExternalIntegrations/{channel-name}. - Wait until
GET /ExternalIntegrations/{channel-name}returns the channel. - Verify the expected hardware endpoints appear in Config Tool.
- Map readers, inputs, and outputs to doors or other access control entities.
Runtime:
- Authenticate to the appliance.
- Send
StatusKeepalive. - Send
SetConnected. - Start long polling.
- Poll with one in-flight request per session.
- Retry the same
reqidafter transport failure. - Increment
reqidonly after receiving and processing a complete HTTP 200 response. - Apply
Set,Clear, andPulseoutput commands. - Apply
GrantedFeedbackandDeniedFeedback. - Send
InputStatusUpdateon input transitions. - Send
CardSwipeon credential reads. - Send
PinEntryfor PIN entry. - Send
OfflineDecisiononly for decisions made locally while offline. - Send
SetConnected=falsefor hardware that goes offline. - Call the
HangupUriduring clean shutdown.
Validation:
- Test an unknown card and confirm denied feedback.
- Test a known card and confirm granted feedback and output pulse.
- Test invalid reader, input, and output IDs.
- Test invalid card bit counts.
- Test non-digit PIN input.
- Test keepalive timeout and recovery.
- Test long-poll transport failure and retry behavior.
- Test appliance restart or runtime restart.
- Test full channel replacement without changing stable identity.
- Plugin SDK overview: Server-side Security Center plugin concepts
- Platform SDK overview: Core Security Center SDK concepts
- 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