Door Access Control
This section shows how to add a new provider integration within the "Door Access Control" (D.A.C.) framework of VikBooking. If the provider of your smart locks/devices is not supported by default in VikBooking, you can always build your own custom provider-integration with the ease of a WordPress plugin.
Please notice that the D.A.C. framework is strictly connected to the E4jConnect Channel Manager service, and so using VikBooking alone will not be sufficient to add a custom provider-integration. Using Vik Channel Manager with an active E4jConnect subscription for the "Door Access Control" service is required for coding your own integration and complete the configuration.
You can still follow this documentation if the purpose is to test the development of a custom integration with a provider. The example-plugin described in this documentation can be installed on any WordPress website using VikBooking, no matter if it's the free version or the paid (Pro) version. However, you may not be able to fully test all functionalities due to the missing E4jConnect Channel Manager service.
This is what's needed before starting to code a custom D.A.C. integration for your provider:
- API documentation - find the technical specifications from your provider on how to establish API connections.
- Application Credentials - API connections will require an authentication, make sure to gather your own credentials.
- Device Capabilities - identify what operations your custom integration should be able to perform over the devices/smart locks.
Once the goal of your custom integration is fully defined, you can move onto the development phase!
WordPress Plugin Structure
The custom WordPress plugin should install a new integration in VikBooking for your provider. Such custom integration should implement the required PHP class and related methods. Even if a single PHP file could be sufficient, it is recommended to give your WordPress plugin a neat structure. The screenshot below shows the directory tree of the custom WordPress plugin, with the main plugin file and the integration file for the provider. We are calling the custom WordPress plugin "vbocustomdacprovider" to identify our custom D.A.C. provider and its integration.

The main WordPress plugin file will be vbocustomdacprovider.php. Under the "integrations" folder, we are placing the actual implementation of our custom integration called "Locks Mockup", and the file is /integrations/locks_mockup.php.
The PHP code snippets that follow will not actually show an integration with an existing provider based on HTTP/API connections, it will rather be a "mockup" of how a real integration should be coded to provide the needed functionalities.
Main WordPress plugin file
The code snippet below shows the full source code of the file vbocustomdacprovider.php, which should be placed in the root directory of the plugin /vbocustomdacprovider.
<?php
/*
Plugin Name: VikBooking Custom Door Access Control Provider
Plugin URI: https://vikwp.com/plugin/vikbooking
Description: Custom Door Access Control Provider implementation.
Author: E4J s.r.l.
Author URI: https://vikwp.com
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: vbocustomdacprovider
*/
// no direct access
defined('ABSPATH') or die('No script kiddies please!');
// register callback for filter "vikbooking_load_door_access_integrations"
add_filter('vikbooking_load_door_access_integrations', function($return) {
if (!$return) {
// normalize return value
$return = [];
}
/**
* Since multiple plugins could add their own custom integrations, it
* is necessary to always merge the paths to the DAC integration files of
* this plugin to any previous integration that may have already been injected.
* Simply define an array of absolute paths to the PHP files that declare
* the needed class by VikBooking to load a DAC provider integration.
*/
return array_merge(
$return,
[
// WP_PLUGIN_DIR = absolute server path to plugin's directory
// vbocustomdacprovider = the name of this custom plugin
// integrations = a private/internal folder of this custom plugin
// locks_mockup.php = the class file that declares the DAC integration
WP_PLUGIN_DIR . '/vbocustomdacprovider/integrations/locks_mockup.php',
]
);
}); VikBooking main integration file
The goal of your custom provider integration is to define the resources to read the remote devices/smart-locks by using specific account credentials, define their capabilities and eventually implement the necessary functions to generate passcodes (access codes).
Our custom integration plugin will implement the following functionalities:
- Define the parameters required to connect to the provider's APIs.
- Multiple profiles are always supported, so the parameters will return the settings (credentials) of the currently active profile.
- Fetch the remote devices, ideally by establishing an API connection with the remote provider API endpoints.
- Define ("decorate") the capabilities for each device, hence what actions the device can actually perform (i.e. generate passcodes, unlock/open etc..).
- Implement the capabilities of the various devices (i.e. "Unlock", "Lock", "See Passcodes", "Generate Passcode", "Delete Passcode").
- Define the action to perform in case of new bookings (website or OTA), we create passcodes with a validity limited to the booking stay dates.
- Define the action to perform in case of booking modification (website or OTA), we update the passcodes.
- Define the action to perform in case of booking cancellation (website or OTA), we delete the passcodes that were previously created.
This custom integration will be fully compatible with the VikBooking framework requirements, and even if we are just showing an example (a mockup), you can test it on your own WordPress website to see how it looks like.
The code snippet below shows the full source code of the file locks_mockup.php, which should be placed inside the "integrations" directory of the plugin: /vbocustomdacprovider/integrations.
<?php
/**
* @package VikBooking
* @subpackage vbocustomdacprovider
* @author E4J s.r.l.
* @copyright Copyright (C) 2025 E4J s.r.l. All Rights Reserved.
* @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
* @link https://vikwp.com
*/
// No direct access
defined('ABSPATH') or die('No script kiddies please!');
/**
* Integration mockup for the Door Access Control framework.
* Make sure the class name matches the name of this file,
* and that the native (abstract) class of VikBooking is extended.
*/
final class VBODooraccessProviderLocksMockup extends VBODooraccessIntegrationAware
{
/**
* @inheritDoc
*/
public function getName()
{
return 'Locks Mockup';
}
/**
* @inheritDoc
*/
public function getShortName()
{
return 'mockup';
}
/**
* @inheritDoc
*/
public function getIcon()
{
return '<i class="' . VikBookingIcons::i('lock') . '"></i>';
}
/**
* @inheritDoc
*/
public function getParams()
{
return [
'username' => [
'type' => 'text',
'label' => __('Account Username', 'vbocustomdacprovider'),
],
'password' => [
'type' => 'text',
'label' => __('Account Password', 'vbocustomdacprovider'),
],
];
}
/**
* @inheritDoc
*/
public function canUnlockDevices()
{
/**
* @todo Tell the framework whether remote devices can be unlocked/opened through APIs.
* The Door Access Control framework may invoke the unlock/opening of specific
* devices connected to your listings upon guest requests (i.e. AI agents).
*/
return true;
}
/**
* @inheritDoc
*/
public function handleUnlockDevice(VBODooraccessIntegrationDevice $device)
{
/**
* @todo Implement your own method for unlocking the current device.
*/
// unlock the requested device
return $this->unlockDevice($device);
}
/**
* @inheritDoc
*/
protected function fetchRemoteDevices()
{
/**
* @todo Implement your own API request by eventually gathering the configured
* profile settings, in this example "username" and "password". We are
* now returning a static list of devices in this example.
*/
// access current profile settings
$settings = $this->getSettings();
// the username and password specified through the parameters defined above
$username = $settings['username'] ?? null;
$password = $settings['password'] ?? null;
// return a dummy list of device properties for later decoration
return [
[
'lockAlias' => 'Main Gate',
'lockId' => 12345678,
'electricQuantity' => rand(1, 100),
'lockName' => 'K1_e0073d',
'groupName' => 'Hotel',
],
[
'lockAlias' => 'Room #1',
'lockId' => 23456789,
'electricQuantity' => rand(1, 100),
'lockName' => 'K2_af197b',
'groupName' => 'Hotel',
],
[
'lockAlias' => 'Room #2',
'lockId' => 34567890,
'electricQuantity' => rand(1, 100),
'lockName' => 'K2_6db4a1',
'groupName' => 'Hotel',
],
[
'lockAlias' => 'Room #3',
'lockId' => 45678901,
'electricQuantity' => rand(1, 100),
'lockName' => 'K2_ce561a',
'groupName' => 'Hotel',
],
[
'lockAlias' => 'Main Gate',
'lockId' => 56789012,
'electricQuantity' => rand(1, 100),
'lockName' => 'K2_00fd63',
'groupName' => 'Apartments',
],
[
'lockAlias' => 'Apartment #1',
'lockId' => 67890123,
'electricQuantity' => rand(1, 100),
'lockName' => 'K2_90eefb',
'groupName' => 'Apartments',
],
[
'lockAlias' => 'Apartment #2',
'lockId' => 78901234,
'electricQuantity' => rand(1, 100),
'lockName' => 'K2_ca380f',
'groupName' => 'Apartments',
],
[
'lockAlias' => 'Apartment #3',
'lockId' => 89012345,
'electricQuantity' => rand(1, 100),
'lockName' => 'K2_df83f4',
'groupName' => 'Apartments',
],
];
}
/**
* @inheritDoc
*/
protected function decorateDeviceProperties(VBODooraccessIntegrationDevice $decorator, array $device)
{
/**
* @todo Implement your own device property decoration. For each previously fetched device object,
* you will be able to decorate and define its own properties and capabilities.
*/
// set device ID
$decorator->setID($device['lockId'] ?? '');
// set device name
$decorator->setName($device['lockAlias'] ?? $device['lockName'] ?? '');
// set device description
$decorator->setDescription($device['groupName'] ?? '');
// set device model
$decorator->setModel($device['lockName'] ?? '');
if ($device['electricQuantity'] ?? null) {
// set device battery level
$decorator->setBatteryLevel((float) $device['electricQuantity']);
}
// set the device capabilities (what it can do)
$decorator->setCapabilities([
// unlock device
$this->createDeviceCapability([
'id' => 'unlock_device',
'title' => __('Unlock', 'vbocustomdacprovider'),
'description' => __('Unlock the device to grant entry.', 'vbocustomdacprovider'),
'icon' => '<i class="' . VikBookingIcons::i('unlock') . '"></i>',
'callback' => 'unlockDevice',
]),
// lock device
$this->createDeviceCapability([
'id' => 'lock_device',
'title' => __('Lock', 'vbocustomdacprovider'),
'description' => __('Lock the device to restrict entry.', 'vbocustomdacprovider'),
'icon' => '<i class="' . VikBookingIcons::i('lock') . '"></i>',
'callback' => 'lockDevice',
]),
// read passcodes
$this->createDeviceCapability([
'id' => 'list_passcodes',
'title' => __('List Passcodes', 'vbocustomdacprovider'),
'description' => __('List all the device passcodes.', 'vbocustomdacprovider'),
'icon' => '<i class="' . VikBookingIcons::i('key') . '"></i>',
'callback' => 'listPasscodes',
]),
// create passcode
$this->createDeviceCapability([
'id' => 'create_passcode',
'title' => __('Create Passcode', 'vbocustomdacprovider'),
'description' => __('Create a random passcode for the device.', 'vbocustomdacprovider'),
'icon' => '<i class="' . VikBookingIcons::i('plus') . '"></i>',
'callback' => 'createPasscode',
'params' => [
'pwd' => [
'type' => 'text',
'label' => __('Passcode', 'vbocustomdacprovider'),
'help' => __('Leave empty to generate a random code.', 'vbocustomdacprovider'),
'attributes' => [
'pattern' => '[1-9][0-9]{3,8}',
],
],
'name' => [
'type' => 'text',
'label' => __('Passcode Name', 'vbocustomdacprovider'),
'help' => __('Optional passcode name for an easier identification.', 'vbocustomdacprovider'),
],
'start' => [
'type' => 'datetime',
'label' => __('Validity Start Date', 'vbocustomdacprovider'),
'help' => __('Passcode validity start date and time.', 'vbocustomdacprovider'),
],
'end' => [
'type' => 'datetime',
'label' => __('Validity End Date', 'vbocustomdacprovider'),
'help' => __('Passcode validity end date and time.', 'vbocustomdacprovider'),
],
],
]),
// delete passcode
$this->createDeviceCapability([
'id' => 'delete_passcode',
'title' => __('Delete Passcode', 'vbocustomdacprovider'),
'description' => __('Delete an existing passcode from the device.', 'vbocustomdacprovider'),
'icon' => '<i class="' . VikBookingIcons::i('trash') . '"></i>',
'callback' => 'deletePasscode',
'params' => [
'tip' => [
'type' => 'custom',
'html' => __('Delete passcode by ID or name.', 'vbocustomdacprovider'),
],
'id' => [
'type' => 'text',
'label' => __('Passcode ID', 'vbocustomdacprovider'),
],
'name' => [
'type' => 'text',
'label' => __('Passcode Name', 'vbocustomdacprovider'),
],
],
]),
]);
// set the full device payload
$decorator->setPayload($device);
}
//////////////////////////////////////////////////////////////////////////
////////////////////////////// CAPABILITIES //////////////////////////////
//////////////////////////////////////////////////////////////////////////
/**
* Device capability implementation to unlock a device.
*
* @param VBODooraccessIntegrationDevice $device The device executing the capability.
* @param ?array $options Optional settings populated from capability parameters.
*
* @return VBODooraccessDeviceCapabilityResult
*/
public function unlockDevice(VBODooraccessIntegrationDevice $device, ?array $options = null)
{
/**
* @todo Implement your own API request for unlocking the current device.
* In this example we are always returning a successful message without doing anything.
*/
return (new VBODooraccessDeviceCapabilityResult)->setText(sprintf(__('The device "%s" was unlocked!', 'vbocustomdacprovider'), $device->getName()));
}
/**
* Device capability implementation to lock a device.
*
* @param VBODooraccessIntegrationDevice $device The device executing the capability.
* @param ?array $options Optional settings populated from capability parameters.
*
* @return VBODooraccessDeviceCapabilityResult
*/
public function lockDevice(VBODooraccessIntegrationDevice $device, ?array $options = null)
{
/**
* @todo Implement your own API request for locking the current device.
* In this example we are always returning a successful message without doing anything.
*/
return (new VBODooraccessDeviceCapabilityResult)->setText(sprintf(__('The device "%s" was locked!', 'vbocustomdacprovider'), $device->getName()));
}
/**
* Device capability implementation to list a device passcodes.
*
* @param VBODooraccessIntegrationDevice $device The device executing the capability.
* @param ?array $options Optional settings populated from capability parameters.
*
* @return VBODooraccessDeviceCapabilityResult
*
* @throws Exception
*/
public function listPasscodes(VBODooraccessIntegrationDevice $device, ?array $options = null)
{
/**
* @todo Implement your own API request for reading the existing passcodes from the current device.
* In this example we are reading the information from a database table of VikBooking that is
* used to store the various passcodes that were generated.
*/
// build HTML output
$output = '';
// lang defs
$lang_passcodeid = __('Passcode ID', 'vbocustomdacprovider');
$lang_passcodenm = __('Passcode Name', 'vbocustomdacprovider');
$lang_passcode = __('Passcode', 'vbocustomdacprovider');
$lang_startdate = __('Validity Start Date', 'vbocustomdacprovider');
$lang_enddate = __('Validity End Date', 'vbocustomdacprovider');
$lang_createdon = __('Created on', 'vbocustomdacprovider');
$lang_createdby = __('Created by', 'vbocustomdacprovider');
// table head
$output .= <<<HTML
<div class="vbo-dac-table-wrap">
<table class="vbo-dac-table">
<thead>
<tr>
<td>{$lang_passcodeid}</td>
<td>{$lang_passcodenm}</td>
<td>{$lang_passcode}</td>
<td>{$lang_startdate}</td>
<td>{$lang_enddate}</td>
<td>{$lang_createdon}</td>
<td>{$lang_createdby}</td>
</tr>
</thead>
<tbody>
HTML;
// load the passcodes for the current ID
$passcodes = VBOFactory::getConfig()->getArray('passcodes_' . $device->getID(), []);
// scan all passcodes obtained
foreach ($passcodes as $passcode) {
// set passcode properties
$passcodeId = $passcode['id'] ?? '';
$passcodeValue = $passcode['pwd'] ?? '';
$passcodeName = $passcode['name'] ?? '';
$startDate = $passcode['start'] ?: '/';
$endDate = $passcode['end'] ?: '/';
$createdDate = $passcode['created'] ?: '/';
$author = $passcode['author'] ?? '';
// bind passcode id values
$passcodesAssoc[$passcodeId] = [
'name' => $passcodeName,
'value' => $passcodeValue,
];
// build passcode HTML code
$output .= <<<HTML
<tr>
<td><span class="vbo-dac-table-passcode-id">{$passcodeId}</span></td>
<td><span class="vbo-dac-table-passcode-name">{$passcodeName}</span></td>
<td><span class="vbo-dac-table-passcode-code">{$passcodeValue}</span></td>
<td>{$startDate}</td>
<td>{$endDate}</td>
<td>{$createdDate}</td>
<td>{$author}</td>
</tr>
HTML;
}
// table head
$output .= <<<HTML
</tbody>
</table>
</div>
HTML;
// return the capability result object by setting the output value
return (new VBODooraccessDeviceCapabilityResult($passcodesAssoc))
->setOutput($output);
}
/**
* Device capability implementation to create a random passcode for a device.
*
* @param VBODooraccessIntegrationDevice $device The device executing the capability.
* @param ?array $options Optional settings populated from capability parameters.
*
* @return VBODooraccessDeviceCapabilityResult
*/
public function createPasscode(VBODooraccessIntegrationDevice $device, ?array $options = null)
{
/**
* @todo Implement your own API request for generating a passcode on the current device.
* In this example we do not perform any API request, but we store the information
* on a database table of VikBooking for being able to retrieve the details afterwards.
*/
if (empty($options['name'])) {
throw new Exception('Missing passcode name.', 400);
}
if (empty($options['pwd'])) {
$options['pwd'] = VikBooking::getCPinInstance()->generateSerialCode(8, ['0123456789']);
}
if (!preg_match("/^[1-9][0-9]{3,8}$/", (string) $options['pwd'])) {
throw new Exception('Malformed code.', 400);
}
$passcode = [
'name' => $options['name'],
'pwd' => $options['pwd'],
'start' => $options['start'] ?? null,
'end' => $options['end'] ?? null,
'created' => date('Y-m-d H:i:s'),
'author' => JFactory::getUser()->username,
];
$config = VBOFactory::getConfig();
// obtain list of registered passcodes for the current device
$passcodes = $config->getArray('passcodes_' . $device->getID(), []);
$existingIds = array_column($passcodes, 'id');
do {
// repeat as long as the ID already exists
$passcode['id'] = VikBooking::getCPinInstance()->generateSerialCode(8, ['0123456789']);
} while (in_array($passcode['id'], $existingIds));
$passcodes[] = $passcode;
// commit changes
$config->set('passcodes_' . $device->getID(), $passcodes);
// build result properties to bind
$resultProps = [
'pwd' => $passcode['pwd'],
'id' => $passcode['id'],
];
return (new VBODooraccessDeviceCapabilityResult($resultProps))
->setPasscode($resultProps['pwd'])
->setText(sprintf(__('Passcode %s successfully generated for the device "%s".', 'vbocustomdacprovider'), $resultProps['pwd'], $device->getName()));
}
/**
* Device capability implementation to revoke a passcode.
*
* @param VBODooraccessIntegrationDevice $device The device executing the capability.
* @param ?array $options Optional settings populated from capability parameters.
*
* @return VBODooraccessDeviceCapabilityResult
*/
public function deletePasscode(VBODooraccessIntegrationDevice $device, ?array $options = null)
{
/**
* @todo Implement your own API request for deleting a passcode from the current device.
* In this example we do not perform any API request, but we delete the information
* from a database table of VikBooking where data was stored upon creating a passcode.
*/
if (empty($options['id']) && empty($options['name'])) {
throw new Exception('Missing both passcode id and name.', 400);
}
$config = VBOFactory::getConfig();
// obtain list of registered passcodes for the current device
$passcodes = $config->getArray('passcodes_' . $device->getID(), []);
$pk = !empty($options['id']) ? 'id' : 'name';
// get the index of the existing ID/name
$posFound = array_search($options[$pk], array_column($passcodes, $pk));
if ($posFound === false) {
// an error occurred
throw new Exception('Error deleting passcode from device.', 404);
}
// remove passcode from array
array_splice($passcodes, $posFound, 1);
$config->set('passcodes_' . $device->getID(), $passcodes);
return (new VBODooraccessDeviceCapabilityResult)
->setText(sprintf(__('The passcode was successfully deleted from the device "%s".', 'vbocustomdacprovider'), $device->getName()));
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////// BOOKINGS ////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/**
* @inheritDoc
*/
public function createBookingDoorAccess(VBODooraccessIntegrationDevice $device, int $listingId, VBOBookingRegistry $registry)
{
/**
* @todo If you have already implemented a device capability for creating a passcode on a device,
* prepare the information from the current booking (reservation) registry to create a new passcode.
*/
// prepare the options for creating a random passcode
$options = [
// use a password name that can be used later to find it under this booking and listing ID
'name' => sprintf('bid:%d-%d', $registry->getID(), $listingId),
// set the passcode validity start date and time
'start' => date('Y-m-d H:i:00', $registry->getProperty('checkin', 0)),
// set the passcode validity end date and time
'end' => date('Y-m-d H:i:00', $registry->getProperty('checkout', 0)),
];
// create passcode on the current device
return $this->createPasscode($device, $options);
}
/**
* @inheritDoc
*/
public function modifyBookingDoorAccess(VBODooraccessIntegrationDevice $device, int $listingId, VBOBookingRegistry $registry)
{
/**
* @todo If you have already implemented a device capability for creating and deleting passcodes on a device,
* prepare the information from the current booking (reservation) registry and call your own methods.
*/
// searching, deleting and re-creating passcodes is always safer in case of
// booking modification for possibly different listing IDs involved
// find the passcode data that were previously created for this booking
$previousDevicePasscodes = VikBooking::getBookingHistoryInstance($registry->getID())
->getEventsWithData(['ND', 'MD'], function($data) use ($device) {
$data = (array) $data;
// ensure the passcode was generated for this provider, profile and device
return ($data['provider'] ?? '') == $this->getProfileProvider() &&
($data['profile'] ?? '') == $this->getProfileID() &&
($data['device'] ?? '') == $device->getID() &&
(!empty($data['passcode']) || !empty($data['props']));
});
if (!$previousDevicePasscodes) {
// no passcodes were previously created for this booking
// process the modification as a new door access creation (with TTLock random passcode)
return $this->createBookingDoorAccess($device, $listingId, $registry);
}
// scan all previously created passcodes in DESC order on this device and delete them
$previousPasscodeIds = [];
foreach (array_reverse($previousDevicePasscodes) as $previousData) {
// ensure we only have array values
$previousData = (array) json_decode(json_encode($previousData), true);
// get the previous passcode
$previousPasscode = ($previousData['passcode'] ?? '') ?: ($previousData['props']['id'] ?? '');
if (empty($previousPasscode) || in_array($previousPasscode, $previousPasscodeIds)) {
// no passcode ID to delete, or already deleted
continue;
}
// push processed passcode ID
$previousPasscodeIds[] = $previousPasscode;
try {
// delete previous passcode for this booking
$this->deletePasscode($device, [
'id' => $previousPasscode,
]);
} catch (Exception $e) {
// do nothing on error
}
}
// process the modification as a new door access creation, but with custom passcodes
return $this->createBookingDoorAccess($device, $listingId, $registry);
}
/**
* @inheritDoc
*/
public function cancelBookingDoorAccess(VBODooraccessIntegrationDevice $device, int $listingId, VBOBookingRegistry $registry)
{
/**
* @todo If you have already implemented a device capability for deleting a passcode from a device,
* prepare the information from the current booking (reservation) registry and call your own method.
*/
// delete passcode by name
return $this->deletePasscode($device, [
'name' => sprintf('bid:%d-%d', $registry->getID(), $listingId),
]);
}
/**
* @inheritDoc
*/
public function getPasscodeFromHistoryResult(array $resultProperties)
{
/**
* @todo Implement your own method to recognize a previously stored passcode.
*/
// creating a passcode should bind its value within the device capability result object
return $resultProperties['pwd'] ?? null;
}
} Conclusion
This WordPress plugin example/mockup is ready for installation. Although it won't actually deal with an existing provider of devices or smart locks, it will show you how to build your own integration. It is fully compatible with the AI agent function tools of E4jConnect, and it can generate access codes for the devices connected to your listings either at the time of booking (creation, modification and cancellation), or before the check-in.
The crucial sections and methods of your integration in the PHP code are highlighted with the @todo comment.
You can partially test this plugin even with the free version of VikBooking, it is necessary to install and activate the plugin on your WordPress website, and then by opening the admin-widget "Door Access Control" in VikBooking you will find the "Locks Mockup" integration among the existing providers. For fully testing it, Vik Channel Manager and an active E4jConnect subscription for the "Door Access Control" service are required as described above.
Please keep in mind during your development that the fetched devices are stored onto the database of VikBooking as serialized blobs. This means that changes to such serialized PHP objects may require a re-synchronization to take effect. To re-sync your devices, use the apposite "Synchronize" button in the "Devices" tab of the admin-widget "Door Access Control" in VikBooking. The operation will keep the existing relations between the devices and listings.
Happy coding!