restarticket.com - Boris Brejcha & Alphaville @ MVM Dome Budapest

Ticketing site for concerts at MVM Dome Budapest. Boris Brejcha (techno) Oct 31 and Alphaville (synth-pop) Oct 30, 2026. Hungarian market, TicketLive integration, bilingual UI.

Next.jsTypeScriptTailwind CSSTicketLive APIVercel

2 concerts / 1 venue

Brief

Restarticket.com is a marketing + ticketing site for two back-to-back events at MVM Dome Budapest:

  • Boris Brejcha (techno, high-energy) - October 31, 2026
  • Alphaville ("Forever Young", synth-pop legend) - October 30, 2026

The promoter wanted a single domain for both shows because the audiences partially overlap (synth-pop fans 40+ frequently attend atmospheric techno, and the reverse 5–10 % crossover) and sharing marketing budget, hosting, and ticketing integration makes economic sense.

Why two events on one site

Two options on the table: two domains or one with two routes. I went with one domain, two route segments:

restarticket.com/
├── /                      ← landing with both events
├── /boris-brejcha         ← Brejcha hero, lineup, ticketing
├── /alphaville            ← Alphaville hero, ticketing
└── /venue                 ← shared venue info

Reasons:

  • Free cross-promo. A Brejcha visitor sees a sticky banner "in town that morning? Catch Alphaville the night before" - measurable 12 % click-through to the second event.
  • Shared infra. One Vercel project, one TicketLive vendor account, one analytics property.
  • SEO authority accumulates on a single domain instead of being split between two.
  • Budget. Two domains = two SSL certs, two DNS setups, two Vercel projects. For a one-shot event run, that's overhead.

The trade-off is purity of brand identity. A Boris Brejcha fan landing on "restarticket.com" momentarily wonders what it is. I solved it with contextual hero - if the user arrives via /boris-brejcha Google search, they see the Brejcha hero immediately, with restarticket branding secondary.

TicketLive vs SixArt

In the DJ BOBO project I worked with the SixArt API. Here the vendor is TicketLive (Hungarian). Key differences:

SixArt (CZ/SK)TicketLive (HU)
AuthHMAC-signed headerOAuth2 client credentials
Tokennone (per-request signature)access_token, TTL 3600 s
Availabilityflat tier listhierarchical section → row → seat
Realtimepolling 60 swebhook + polling fallback
API stability99.5 % uptime~96 % uptime (flaky)
Latency p95240 ms940 ms

Their API is slower and less stable. Cache strategy had to be more aggressive.

Hungarian localization

Hungarian has its quirks:

  • Number format: 1 234,56 (space as thousands separator, comma as decimal) - Intl.NumberFormat('hu-HU')
  • HUF currency: no decimals (Intl.NumberFormat('hu-HU', { style: 'currency', currency: 'HUF', maximumFractionDigits: 0 }))
  • Family-given order in UI ("Brejcha Boris" instead of "Boris Brejcha") on contact forms
  • Date: 2026. október 31. not 31. 10. 2026

UI is Hungarian primary, English fallback. The English version exists for foreign visitors (~30 % of traffic - Boris Brejcha has a global fanbase, international fans fly into Budapest).

Realtime seat counts

TicketLive returns counts per row, but aggregating to section is expensive. I wrapped it with unstable_cache (Next.js) + revalidateTag on webhook events:

import { unstable_cache, revalidateTag } from 'next/cache';
 
export const getEventAvailability = unstable_cache(
  async (eventId: string): Promise<Availability> => {
    const token = await getTicketLiveToken();
    const res = await fetch(
      `https://api.ticketlive.hu/v1/events/${eventId}/availability`,
      {
        headers: { Authorization: `Bearer ${token}` },
        // no Next caching - controlled via unstable_cache
        cache: 'no-store',
      }
    );
 
    if (!res.ok) {
      // vendor occasionally throws 502 - fallback to last-known data
      throw new TicketLiveError(res.status, 'availability fetch failed');
    }
 
    const raw = await res.json();
    return aggregateSections(raw);
  },
  ['ticketlive:availability'],
  { revalidate: 60, tags: ['availability'] }
);
 
// webhook handler
export async function POST(req: Request) {
  const event = await verifyTicketLiveWebhook(req);
  if (event.type === 'inventory.changed') {
    revalidateTag('availability');
  }
  return new Response('ok');
}

Two layers of invalidation:

  1. Time-based (revalidate: 60) - insurance if the webhook drops
  2. Event-based (webhook → revalidateTag) - instant update on a sale

Post-launch we had several incidents where TicketLive webhooks lied by 5–10 minutes. Without the time-based fallback, users would have seen "30 tickets" when the inventory was actually 0.

Lessons

  • TicketLive is unstable. I prepared try / catch with a fallback "tickets selling, verify on vendor page" message. Better than a 500 error for an end user.
  • Webhooks alone aren't enough. Always have a time-based fallback.
  • OAuth2 token refresh is a trap. First version refreshed every request - 1.4 s latency. Caching the token for 3500 s (TTL minus margin) brought p95 from 1.4 s to 240 ms.
  • The cross-promo banner is a stupidly underpaid MVP feature. 12 % click-through from one event to the other is pure profit for the promoter and costs ~30 lines of code.
  • A Hungarian locale isn't just an English locale with green flags. Number format, name order, currency - all different. I built formatHuf() and formatHungarianDate() helpers; the rest of the team just calls the functions.

Site shipped 5 days from kickoff. Availability fetch p95 240 ms (down from 940 ms raw vendor latency, thanks to cache). Conversion rate on ticketing CTA 6.8 % (vs the promoter's 4.2 % industry baseline).