QR Integration Guide
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:
Working example (with secret keys 123456 + ABCDEF):
To generate a valid QR you need three things:
- Your issuer code (4 characters, provided by Roommatik)
- Your issuer secret key and the hotel secret key (provided by Roommatik)
- 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
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
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 |
YYYYMMDDTHHMME.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
- Extract the payload: everything between
<and the last|(neither included). In the example:ACME|20210412T1800|20210413T1200|20210415T1330|101|N|000A|ab3245re12|2 - Build the secret key: concatenate the issuer’s secret + the hotel’s secret. Example:
123456+ABCDEF=123456ABCDEF - Compute HMAC-SHA1 of the payload using the concatenated key as the secret
- Encode the HMAC result (20 bytes) in Base64 — this is the
SIGNATUREvalue
Code Examples
Python
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
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.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
QR Code Generation
- 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:
- freeformatter.com/hmac-generator — online HMAC generator (select SHA1)
- base64.guru/converter/encode/hex — convert hex to Base64
