Security and trust

What protects a ticket, and what protects your money.

Specific enough to check. Every claim on this page is a property of the running system, and the last section is the list of things we deliberately do not claim.

Tickets

  • A ticket is signed, not numbered

    Every QR carries a cryptographically signed token rather than a database id. An id in a QR is a credential with no integrity — print a few, notice they are guessable, and you are inside. A code we did not issue cannot be made to verify.

    Signed with a key used for nothing else, so a leaked ticket key cannot mint logins.

  • The QR image is drawn by us

    Not by a public QR-generating service with the admission token in a query string. That is a common shortcut and it hands a third party the credential that opens the door, for every ticket ever sold, in their access logs.

    Served with no-store and a policy that blocks the image from loading anything.

  • One admission per ticket

    Two gates scanning the same code at the same instant produce exactly one entry and one refusal — decided by a lock on the ticket row, not by whichever request happened to arrive first.

    And the refusal carries the time of the first scan.

  • One transfer, and it is recorded

    A ticket can change hands once, when the organizer allows it. The old code stops working the moment the new one is issued.

Money

  • We never see a card number

    Payment happens on Stripe’s own hosted page, which the browser is sent to. No card field on this site ever holds a card, because there is no card field on this site.

    Stripe is a PCI DSS Level 1 service provider. We are a merchant that redirects to it.

  • Money goes to the organizer, not to us

    Card payments are charged directly to the organizer’s own Stripe account. We hold no float and operate no escrow balance, so there is no pool of other people’s money here to lose.

  • Prices are integer cents, everywhere

    No floating-point arithmetic touches money at any layer — not the database, not the API, not the browser. The frontend’s money module can format a price and cannot add two together, and a test asserts it exports nothing that could.

    parseFloat("19.99") * 100 is 1998.9999999999998. That is the whole reason.

Accounts

  • Sessions are checked, not just decoded

    The session is an httpOnly cookie the browser holds and JavaScript cannot read, and every single request re-checks it against a row in the database. Signing out on one device revokes it everywhere immediately — it does not merely delete a cookie.

  • Passwords are hashed with PBKDF2-HMAC-SHA512

    210,000 iterations, the OWASP figure, with a per-password salt. The stored value names its own cost, so the cost can be raised later without invalidating anyone.

  • The browser never queries the database

    Every read and write goes through the API, where authorisation is enforced. This is not a convention — it is a lint rule that fails the build, because a convention decays the first time someone reaches for a quick read.

  • Credential endpoints are rate limited

    Sign-in, password reset, email verification codes and door sign-in are all throttled per address. A door PIN is short by necessity, so it is limited hardest.

  • An address is proven before it is trusted

    A new account is not signed in until it enters the six-digit code we email it. The code expires in ten minutes, works once, and dies after five wrong guesses; only a keyed hash of it is ever stored.

The door

  • Two ways in, both narrow

    A door runs from a shared tablet registered with its own PIN, or from named door staff signing in with their own accounts. Nobody at the door needs the organizer’s password, and either kind of session can scan that event and nothing else — no orders, no money, no settings.

  • Revoking takes effect at once

    Not at the next sign-in. The moment the organizer switches a device off or removes someone from the door team, the session they already hold stops working — every scan re-checks that it is still allowed.

    A revocation that only takes effect later is a false assurance, which is worse than no button.

  • A door session opens one event

    The event is inside the session’s token and is never read from the request, so one venue’s scanner cannot admit another venue’s guests — or reverse their admissions — by changing a field.

What we do not claim

A platform’s refusals are more informative than its badges. If you need one of these for your own compliance, ask before you commit an event to us — the answer will be this list, said plainly.

SOC 2
We have not been audited. If that changes, this line changes.
ISO 27001
Not certified.
PCI DSS compliance of our own
Card data never reaches us; Stripe carries that burden and we redirect to them. We do not claim a certification we have no scope for.
An independent penetration test
Not yet commissioned.
A bug bounty programme
None yet. Reports are still welcome, and read by a person.

Reporting something

If you have found a vulnerability, write to info@eventsli.com with Security in the subject. Tell us what you did and what happened; a proof of concept helps more than a scanner report. Please do not test against a live event that other people have bought tickets to — ask us and we will point you somewhere safe.

We read these ourselves. There is no bounty, and we will not pretend otherwise to get a report.