QR Integration Guide

Document version 3.0 — Last updated: March 2026.
Roommatik kiosks and key dispensers interpret QR codes in the format described below to dispense a coded key based on the information contained within. QR codes can be generated completely offline.

Quick Start

A valid Roommatik QR code has this structure:

<ISSUER|ISSUE_DT|ACTIVATION_DT|EXPIRY_DT|ROOM|TYPE|DOORS|QR_ID|MAX_CARDS|SIGNATURE>

Working example (with secret keys 123456 + ABCDEF):

<ACME|20210412T1800|20210413T1200|20210415T1330|101|N|000A|ab3245re12|2|IeuEuUyoxK8rsaONgn24hQ5UJbg=>

To generate a valid QR you need three things:

  1. Your issuer code (4 characters, provided by Roommatik)
  2. Your issuer secret key and the hotel secret key (provided by Roommatik)
  3. Reservation data (room, dates, etc.)

Integration Options

Option Use case PMS connection
Option 1 — Full key data in QR Room is already assigned (e.g. issuing keys for checked-in guests) Not required
Option 2 — Reservation number in QR Room is not yet assigned at QR generation time Required (room & checkout queried at scan time)

Option 1 — Full Key Data in QR

All information necessary to encode the key is included in the QR code, including the room number. The Roommatik device only needs to connect with the lock software server.

Format

<ABCD|ISSUE_DATE_TIME|ACTIVATION_DATE_TIME|EXPIRY_DATE_TIME|ROOM_IDENTIFIER|CARD_TYPE|COMMON_DOORS_MASK|QR_UNIQUE_ID|MAX_CARDS_PER_QR|SIGNATURE>

Option 2 — Reservation Number in QR

The QR code contains only the reservation number. Roommatik dispensers and kiosks will query the room number and checkout date from the PMS at scan time.

Format

<ABCD|ISSUE_DATE_TIME|RESERVATION_NUMBER|COMMON_DOORS_MASK|QR_UNIQUE_ID|MAX_CARDS_PER_QR|SIGNATURE>

Field Reference

Field Description Format / Values
ABCD Unique identifier of the QR code issuer. Provided by Roommatik Max 4 alphanumeric characters
ISSUE_DATE_TIME Date/time from which the device can issue the key. The QR is only valid between this instant and EXPIRY_DATE_TIME YYYYMMDDTHHMM
E.g. 20260315T1400
ACTIVATION_DATE_TIME Start date/time encoded on the card. If omitted (empty field), the issuance date/time is used YYYYMMDDTHHMM (optional)
EXPIRY_DATE_TIME Expiration date/time encoded on the card (typically checkout time) YYYYMMDDTHHMM
ROOM_IDENTIFIER Unique room identifier. Must match exactly the ID used in the lock software (case-sensitive) Max 8 characters
CARD_TYPE Type of key card to issue N = New (invalidates previous cards)
C = Copy (previous cards remain valid)
COMMON_DOORS_MASK Hexadecimal mask for common door permissions. See detailed explanation below 4 hex characters (2 bytes = 16 doors)
E.g. 000A
QR_UNIQUE_ID Unique identifier of the QR code. Used for traceability and hotel logs Max 10 characters
MAX_CARDS_PER_QR Maximum keys to issue for this QR. The effective limit is the lesser of this value and the device-configured maximum Integer (optional, empty = unlimited)
RESERVATION_NUMBER Reservation identifier (Option 2 only). The PMS returns room and checkout date String
SIGNATURE HMAC-SHA1 signature of the payload, Base64-encoded. See signature calculation 28 Base64 characters

Common Doors (COMMON_DOORS_MASK)

The mask is a 4-character hex value (2 bytes) where each bit represents a door. The least significant bit (LSB) corresponds to door 0.

Hex example Binary Active doors
0000 0000 0000 0000 0000 None
0001 0000 0000 0000 0001 Door 0
000A 0000 0000 0000 1010 Doors 1 and 3
0007 0000 0000 0000 0111 Doors 0, 1 and 2
FFFF 1111 1111 1111 1111 All (0–15)

To calculate the mask: sum 2^n for each door n you want to enable, then convert the result to hexadecimal padded to 4 characters.

Signature Calculation

The signature ensures the QR code was generated by an authorized issuer. If the signature is incorrect, the dispenser will reject the code.
  1. Extract the payload: everything between < and the last | (neither included). In the example: ACME|20210412T1800|20210413T1200|20210415T1330|101|N|000A|ab3245re12|2
  2. Build the secret key: concatenate the issuer’s secret + the hotel’s secret. Example: 123456 + ABCDEF = 123456ABCDEF
  3. Compute HMAC-SHA1 of the payload using the concatenated key as the secret
  4. Encode the HMAC result (20 bytes) in Base64 — this is the SIGNATURE value

Code Examples

Python

import hmac, hashlib, base64

def generate_roommatik_qr(
issuer: str,
issuer_secret: str,
hotel_secret: str,
issue_dt: str, # “YYYYMMDDTHHMM”
activation_dt: str, # “YYYYMMDDTHHMM” or “”
expiry_dt: str, # “YYYYMMDDTHHMM”
room: str,
card_type: str, # “N” or “C”
doors_mask: str, # “0000”
qr_id: str,
max_cards: str = “”, # “” = unlimited
) -> str:
fields = [issuer, issue_dt, activation_dt, expiry_dt,
room, card_type, doors_mask, qr_id, max_cards] payload = “|”.join(fields)
key = (issuer_secret + hotel_secret).encode()
sig = hmac.new(key, payload.encode(), hashlib.sha1).digest()
signature = base64.b64encode(sig).decode()
return f”<{payload}|{signature}>”

# Example
qr = generate_roommatik_qr(
issuer=”ACME”,
issuer_secret=”123456″,
hotel_secret=”ABCDEF”,
issue_dt=”20210412T1800″,
activation_dt=”20210413T1200″,
expiry_dt=”20210415T1330″,
room=”101″,
card_type=”N”,
doors_mask=”000A”,
qr_id=”ab3245re12″,
max_cards=”2″,
)
print(qr)
# <ACME|20210412T1800|…|IeuEuUyoxK8rsaONgn24hQ5UJbg=>

JavaScript / Node.js

import { createHmac } from “crypto”;

function generateRoommatikQR({
issuer, // “ACME”
issuerSecret, // “123456”
hotelSecret, // “ABCDEF”
issueDt, // “YYYYMMDDTHHMM”
activationDt, // “YYYYMMDDTHHMM” or “”
expiryDt, // “YYYYMMDDTHHMM”
room, // “101”
cardType, // “N” | “C”
doorsMask, // “0000”
qrId, // “ab3245re12”
maxCards = “”, // “” = unlimited
}) {
const fields = [issuer, issueDt, activationDt, expiryDt,
room, cardType, doorsMask, qrId, maxCards];
const payload = fields.join(“|”);
const key = issuerSecret + hotelSecret;
const sig = createHmac(“sha1”, key).update(payload).digest(“base64”);
return `<${payload}|${sig}>`;
}

// Example
const qr = generateRoommatikQR({
issuer: “ACME”,
issuerSecret: “123456”,
hotelSecret: “ABCDEF”,
issueDt: “20210412T1800”,
activationDt: “20210413T1200”,
expiryDt: “20210415T1330”,
room: “101”,
cardType: “N”,
doorsMask: “000A”,
qrId: “ab3245re12”,
maxCards: “2”,
});
console.log(qr);

C#

using System.Security.Cryptography;
using System.Text;

string GenerateRoommatikQR(
string issuer, string issuerSecret, string hotelSecret,
string issueDt, string activationDt, string expiryDt,
string room, string cardType, string doorsMask,
string qrId, string maxCards = “”)
{
var fields = new[] { issuer, issueDt, activationDt, expiryDt,
room, cardType, doorsMask, qrId, maxCards };
var payload = string.Join(“|”, fields);
var key = Encoding.UTF8.GetBytes(issuerSecret + hotelSecret);
using var hmac = new HMACSHA1(key);
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var signature = Convert.ToBase64String(hash);
return $”<{payload}|{signature}>”;
}

Full Step-by-Step Example

Input Data

Data Value
Issuer code ACME
Issuer secret key 123456
Hotel secret key ABCDEF
QR issue time 12 Apr 2021 at 18:00
Check-in 13 Apr 2021 at 12:00
Check-out 15 Apr 2021 at 13:30
Room 101
Card type N (new, invalidates previous)
Common doors Doors 1 and 3 → 000A
QR ID ab3245re12
Max cards 2

Signature Calculation

Step Value
1. Payload ACME|20210412T1800|20210413T1200|20210415T1330|101|N|000A|ab3245re12|2
2. Combined key 123456ABCDEF
3. HMAC-SHA1 (hex) 21eb84b94ca8c4af2bb1a38d827db8850e5425b8
4. Base64 IeuEuUyoxK8rsaONgn24hQ5UJbg=

Resulting QR

<ACME|20210412T1800|20210413T1200|20210415T1330|101|N|000A|ab3245re12|2|IeuEuUyoxK8rsaONgn24hQ5UJbg=>

QR Code Generation

Recommendations for generating the QR image:

  • Error correction level: M (15%) or higher
  • Encoding: alphanumeric or byte (UTF-8)
  • Minimum size: 3 cm × 3 cm printed
  • Contrast: black on white background, do not invert
  • Quiet zone: keep at least 4 modules of white margin around the QR

Common Errors

Symptom Likely cause Solution
Dispenser rejects the QR Incorrect signature Verify the payload does not include the <, >, or the trailing | characters. Check that keys are concatenated in the correct order (issuer + hotel)
QR “expired” ISSUE_DATE_TIME is in the future or EXPIRY_DATE_TIME has passed Adjust the dates. No timezone is applied: values are interpreted as the device’s local time
Key does not open the door ROOM_IDENTIFIER does not match the lock software Request the configured room ID list from Roommatik
QR reads but does not dispense MAX_CARDS_PER_QR reached Generate a new QR with a different QR_UNIQUE_ID
QR scan error QR too small or low contrast Print at minimum 3 cm × 3 cm, black on white, with margin

Useful Tools

For manually verifying your signature calculation:

Need help? Contact the Roommatik integrations team through the technical support chat in the portal.