Skip to main content

Secure QR Event Pass

Generate a compact event pass whose URL contains a short ticket ID and an HMAC signature. The rendered QR code is produced by this exact payload—not a mock image.

Scannable QR event pass showing the attendee Cruise and a March 18 date

Complete app

app.star
load("hash.star", "hash")
load("hmac.star", "hmac")
load("qrcode.star", "qrcode")
load("render.star", "render")
load("schema.star", "schema")
load("secret.star", "secret")
load("time.star", "time")

ENCRYPTED_SIGNING_KEY = "..."
DEFAULT_EVENT_TIME = "2026-03-18T19:30:00Z"

def main(config):
attendee = config.str("attendee", "Cruise")
event_time = time.parse_time(
config.str("event_time", DEFAULT_EVENT_TIME),
)
key = secret.decrypt(ENCRYPTED_SIGNING_KEY) or "demo-secret"
ticket_id = hash.sha256("%s|%s" % (attendee, event_time.unix))[0:4]
signature = hmac.sha256(key, ticket_id)[0:6]
payload = "https://spx.dev/p/%s?s=%s" % (ticket_id, signature)
code = qrcode.generate(
url = payload,
size = "large",
color = "#ffffff",
background = "#000000",
)

return render.Root(
child = render.Padding(
pad = (1, 0, 0, 0),
child = render.Row(
children = [
render.Padding(
pad = (0, 1, 0, 2),
child = render.Image(src = code),
),
render.Box(width = 3, height = 1),
render.Column(
children = [
render.Padding(
pad = (0, 1, 0, 0),
child = render.WrappedText(
"Leelo multi pass",
width = 32,
height = 17,
font = "CG-pixel-3x5-mono",
linespacing = 1,
color = "#fbbf24",
),
),
render.Box(width = 1, height = 1),
render.Text(
attendee,
font = "CG-pixel-4x5-mono",
color = "#ffffff",
),
render.Box(width = 1, height = 1),
render.Text(
event_time.format("Jan 02"),
font = "CG-pixel-3x5-mono",
color = "#94a3b8",
),
],
),
],
),
),
)

def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.Text(
id = "attendee",
name = "Attendee",
desc = "Name printed on the pass.",
icon = "text",
default = "Cruise",
),
schema.DateTime(
id = "event_time",
name = "Event time",
desc = "Date and time printed on the pass.",
icon = "calendar",
),
],
)

Download this app.star.

How it works

  1. DateTime supplies an ISO timestamp, which time.parse_time() turns into a value.
  2. SHA-256 creates a stable ticket ID without putting the attendee name in the URL.
  3. HMAC signs that ID with a decrypted secret so the server can reject forged passes.
  4. qrcode.generate() produces the actual image bytes consumed by render.Image.

The shortened hashes fit this display but are intentionally small for the tutorial. Use longer server-validated values for real admission systems, and replace the demo fallback with a required encrypted signing key.

Was this helpful?