Skip to content

Repository files navigation

MakePay Event Tickets for BTCPay Server

A self-hosted event storefront, ticket delivery system, POS, and QR admission workflow that turns paid BTCPay invoices into customer tickets.

Version 1.7.3 keeps fully paid Processing invoices reserved through BTCPay's monitoring period, atomically rechecks capacity before ticket issuance, and surfaces payments received after expiration as merchant-visible manual-review orders without issuing tickets automatically.

Version 1.7.2 fixes currency-aware promotion precision, bounded public checkout and invoice creation, expired partial-payment inventory release, Apple Wallet WWDR certificate chains, configured event-timezone conversion and display, and durable retry delivery for paid-order emails.

Version 1.7.0 was a security, reliability, and scalability release. Public orders now always require an independent capability token, legacy hashless records fail closed and receive protected tokens during migration, and payment creation uses durable single-winner claims plus deterministic BTCPay invoice IDs. Fulfillment verifies the exact persisted invoice, atomically issues tickets once, safely reuses complete legacy ticket sets, and periodically recovers interrupted paid orders. Orders and tickets migrate from unbounded store JSON collections into indexed database rows with server-side pagination, aggregate inventory/admission queries, fair bounded recovery, and retention for abandoned uninvoiced reservations. PostgreSQL-backed CI covers the cross-process races.

Version 1.6.3 introduces a dedicated MakePay Event Tickets identity across the BTCPay sidebar, marketplace listing, and documentation-site favicon family. The compact ticket-and-QR mark is vector-native, inherits BTCPay navigation states, and remains legible from 16-pixel browser icons through 512-pixel listing artwork.

Version 1.6.2 adds secure, store-scoped admin order detail pages with invoice-backed historical totals, ticket and attendee snapshots, delivery state, admission and ID-check history, and direct actions that preserve dashboard filters.

Version 1.6.1 adds server-side order search, event and payment-status filters, selectable page sizes, stable numbered pagination, responsive controls, and clear result ranges across the complete reservation history.

Version 1.6.0 turns the protected per-event scanner into a complete door-state workflow. A scan is read-only until staff explicitly checks the holder in or confirms check-out, re-entry is supported and counted, events can require a staff-confirmed photo-ID decision, and confirmed/rejected ID checks are audited per ticket. The dashboard opens each scanner from a share dialog with a QR code, copyable link, direct open action, and link rotation warning.

Version 1.5.0 replaced the global admin scanner with protected, per-event public scanner pages optimized for repeated mobile check-in. Each scanner validates only its event, shows the ticket holder and ticket type, supports camera, saved-image, and manual entry, and closes results automatically after a store-configurable delay (five seconds by default).

Version 1.4.0 brings the Event Tickets dashboard, settings workspace, live editor, and public event directory in line with the Digital Products experience. It also hardens invoice-backed reservation expiry, ended-event and sold-out handling, pending-order refresh, non-cacheable customer pages, consent-aware analytics replay, saved attendee details, BTCPay modal recovery, responsive behavior, and enforced MakePay attribution.

Version 1.3.1 adds a native QR-code sidebar icon that follows BTCPay's light, dark, hover, and active navigation states.

Version 1.3.0 adds native BTCPay App domain mapping, clean branded ticket URLs, and an in-product setup guide with safe DNS/TLS instructions for BTCPay Docker operators.

Features

  • Branded responsive event shop with editable logo, favicon, hero artwork, colors, typography, copy, and a fullscreen live editor covering every checkout state.
  • Multi-ticket cart, promotional pricing, buyer and attendee details, reservation countdowns, and the official BTCPay JavaScript modal (/modal/btcpay.js + showInvoice) with invoices created server-side.
  • Protected post-payment order pages with downloadable multi-page PDF tickets, one real QR code per attendee, and the same secure link delivered by email.
  • Multiple ticket types, capacity controls, quantity limits, draft/published events, and an event-specific POS view.
  • Atomic inventory reservations, Processing-invoice holds through BTCPay's monitoring period, a final capacity guard before issuance, automatic release for unpaid expiration/invalidity, and explicit manual review for late payments.
  • Cryptographically random ticket codes stored as one-way hashes with encrypted recoverable values.
  • Per-event, mobile-first camera QR scanner with a protected capability link, manual and saved-image fallbacks, read-only lookup followed by atomic check-in or confirmed check-out, attendee and ticket-type results, re-entry and entrance counts, optional staff-confirmed photo-ID decisions, audited ID confirmation/rejection counts, and configurable automatic result closing.
  • Adjustable HTML email through Resend or BTCPay store SMTP, with an attached standards-compliant PDF ticket document.
  • Apple Wallet .pkpass generation with merchant-provided Pass Type certificate and Google Wallet signed save links with a service account.
  • Configurable privacy/terms links, attendee notice, optional phone/country/company fields, confirmation copy, and Resend or SMTP delivery templates.
  • Mutually exclusive Google Tag Manager or direct Google Analytics 4 integration with consent controls, Do Not Track support, CSP allowlisting, and normalized ecommerce events throughout the ticket funnel.
  • Enforced BTCPay + MakePay attribution for decentralized acceptance of 90+ currencies.

Analytics setup

Open Event Tickets → Settings → Analytics & ecommerce events, then choose either Google Tag Manager or Google Analytics 4 and enter the matching GTM-… container ID or G-… measurement ID. Selecting one provider avoids duplicate GA reporting. Consent is required by default, Google scripts are not loaded before approval, revocation reloads into a Google-script-free state, and browser Do Not Track is respected by default.

Every public page initializes window.dataLayer and window.makePayAnalytics even when Google is disabled, so a self-hosted integration can consume the same stable contract. Events clear stale ecommerce state before pushing and include a makepay context with plugin: "event_tickets", the store ID, and schema version 1.

Standard events include:

  • Sanitized page_context and page_view, plus view_item_list, select_item, and view_item while browsing events and ticket types.
  • add_to_cart, view_cart, and best-effort browser-deduplicated begin_checkout as a reservation moves through the cart and attendee form.
  • Best-effort browser-deduplicated add_payment_info when the BTCPay modal opens or the protected invoice link is used.
  • purchase only after the order is paid, with best-effort browser deduplication plus a stable one-way analytics transaction ID for GA4 deduplication, ISO currency, value, coupon, and ticket-type line items.

The integration never includes buyer or attendee names, email addresses, phone numbers, companies, countries, ticket codes, invoice IDs, access tokens, payment addresses, raw order IDs, or checkout query parameters. Direct GA4 page locations mask dynamic checkout/order capability segments, and referrers receive the same treatment. Do Not Track disables both Google and local data-layer collection when enabled.

GTM page-view safety

The configured GTM container is merchant-controlled JavaScript and can technically read the browser URL. Checkout and order URLs contain protected capability parameters, so do not use an All Pages automatic pageview trigger or GTM's browser Page URL variable on those routes. Instead, trigger page tracking from MakePay's page_view data-layer event and map its top-level page_location, page_path, and page_referrer values. These remove query strings/fragments and mask checkout/order identifiers. Every ecommerce event carries the same safe page fields. Direct GA4 disables automatic pageviews and sends the sanitized values itself.

Wallet setup

Apple Wallet requires a Pass Type Identifier, Apple Team Identifier, and its .p12 signing certificate/password. Google Wallet requires an issuer ID, an existing Generic Pass class ID, and service-account JSON authorized for that issuer. Wallet secrets are encrypted with BTCPay Server data protection.

Storefront favicon

Set Browser favicon URL under Event Tickets → Experience settings → Brand & storefront. Use an absolute HTTP(S) URL to a square ICO, PNG, or SVG asset. The configured icon is emitted consistently on the event directory, ticket selection, cart, attendee checkout, BTCPay payment, and protected order pages; leaving the field empty emits no favicon link.

Custom domains

The Use your own domain guide in Event Tickets settings shows the current canonical ticket URL, the native App identity used for mapping, a Docker command template, and links to BTCPay's official domain documentation.

DNS and TLS cannot be provisioned by the plugin: the domain owner or BTCPay server operator must point the hostname to the server, keep every existing entry when adding it to BTCPAY_ADDITIONAL_HOSTS, and rerun btcpay-setup.sh. Verify DNS before requesting the certificate, because an unresolved additional hostname can interfere with Let's Encrypt renewal for the other configured names.

BTCPAY_ADDITIONAL_HOSTS aliases the entire BTCPay Server, not only this store. Other BTCPay pages and store-scoped public routes remain reachable through that hostname, so it must not be treated as domain-to-store isolation.

To enable native clean routing:

  1. In Event Tickets → Experience settings, choose Create Event Tickets App. This opens BTCPay's standard create-App screen; loading or saving plugin settings never creates an App as a hidden side effect.
  2. Give the App a clear name. If old duplicate Event Tickets Apps already exist, keep one active identity and archive the others.
  3. As a BTCPay server administrator, open Server Settings → Policies → Domain mapping and map the exact canonical ASCII hostname to that App. Do not include whitespace, a trailing dot, scheme, port, or path; use punycode for an internationalized hostname.
  4. Verify the directory, event, cart, attendee details, embedded BTCPay payment, protected confirmation, status polling, rebuy, PDF, and Apple Wallet routes on https://tickets.example.com/events/….

BTCPay supplies the mapped App ID to the plugin's clean controller through DomainMappingConstraint. The plugin then derives the store only from that AppData; a route, query, or form value named storeId cannot select another store. If multiple App records exist, canonical URL generation follows the App actually selected in Policies rather than assuming the oldest one.

The mapped hostname root redirects to /events. BTCPay deployments configured below a root path preserve it consistently, for example https://tickets.example.com/btcpay/events. Every generated public link uses the mapped host and clean path, including invoice return metadata, email delivery, payment polling, protected order pages, rebuy, PDFs, wallet passes, and each event's scanner. BTCPay resolves the first exact hostname row, so duplicate rows must be removed; a later conflicting row is inactive.

Each dashboard event row has a Scanner action that opens a share dialog. The dialog shows a QR code for staff devices, a copyable protected URL, and Open scanner. Treat the URL as a staff credential: opening it exchanges the protected query token for a renewable 12-hour, HttpOnly, event-path-scoped cookie and immediately redirects to a token-free URL. The dialog also contains Rotate scanner link with an explicit warning that rotation invalidates every previous link and scanner session for that event; rotation is deliberately absent from the event table's quick actions.

Scanning a code first performs a read-only lookup. Staff then explicitly chooses Check in or, for a holder currently inside, confirms Check out. Check-out marks the holder outside so the same ticket can be admitted again; each successful admission increments its entrance count. When Require photo ID check at admission is enabled on an event, staff must confirm that the ID matches or record a rejection before admission. ID confirmations and rejections are counted per ticket for operational review. A rejected ID decision never admits the holder, and check-out never requires another ID decision. The standalone scanner is excluded from storefront analytics and search indexing and only accepts tickets issued for its event.

Existing /stores/{storeId}/events/… routes remain available for compatibility. Once a mapping exists, safe GET and HEAD requests redirect permanently to the canonical hostname while POST requests remain on their submitted origin, avoiding an unsafe cross-host form replay.

Onion requests never canonicalize to the clearnet mapping. Checkout records their route/origin intent, so BTCPay invoice returns and fulfillment email links remain on the onion origin even when the store also has a mapped clearnet hostname.

A dedicated ticket subdomain is recommended. If an apex domain already serves another website, that site's reverse proxy or CDN must deliberately forward /events and all nested public routes to BTCPay. Separate hostnames are required when Event Tickets and Digital Products should each use native clean URLs because BTCPay maps one hostname to one App.

Official references: Docker domain settings, BTCPay App domain mapping, and external reverse-proxy requirements.

Build and test

git submodule update --init --recursive
dotnet test -c Release -p:RazorCompileOnBuild=true

Requires BTCPay Server 2.3.5 or newer and .NET 8.

Created by MakePay.io — accept 90+ currencies in a decentralized way in BTCPay Server.

About

Event ticket storefront, POS, QR check-in, email, PDF, and wallet passes for BTCPay Server by MakePay.io

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages