Zum Hauptinhalt springen
🪟 Windows TippsOffice 2024 Pro Plus zum Top-Preis! Lizenzen ab 16,66 €(18.09.2026 um 04:55 Uhr)
🕵️ SicherheitslückenCVE-2026-93468 | HGiga OAKclouds up to 106 path traversal (EUVD-2026-82642)(18.09.2026 um 05:10 Uhr)
🪟 Windows TippsOffice 2024 Pro Plus zum Top-Preis! Lizenzen ab 16,66 €(18.09.2026 um 04:55 Uhr)
🕵️ SicherheitslückenCVE-2026-93468 | HGiga OAKclouds up to 106 path traversal (EUVD-2026-82642)(18.09.2026 um 05:10 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

How to generate a PDF ticket or boarding pass automatically

How to Generate a PDF Ticket or Boarding Pass Automatically

Event platforms generate tickets, but they're branded with the platform's chrome. If you're running a conference, a workshop, a transit system, or any booking flow on your own infrastructure, you need to generate tickets yourself.

Here's how to render a professional PDF ticket — with QR code, barcode, seat info, and event details — triggered on booking confirmation.

Event ticket template

function renderEventTicketHtml({ ticket }) {
  const {
    eventName, eventDate, eventTime, venue, venueAddress,
    attendeeName, ticketId, ticketType, seatSection, seatRow, seatNumber,
    orderId, doorTime,
  } = ticket;

  return `<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    @page { size: 8.5in 3.5in; margin: 0; }
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      width: 8.5in;
      height: 3.5in;
      font-family: -apple-system, 'Segoe UI', sans-serif;
      display: flex;
      overflow: hidden;
    }

    /* Left panel — event info */
    .main {
      flex: 1;
      background: #111;
      color: white;
      padding: 0.4in 0.4in;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      position: relative;
      overflow: hidden;
    }
    .main::after {
      content: '';
      position: absolute;
      top: -60px; right: -60px;
      width: 200px; height: 200px;
      border-radius: 50%;
      background: rgba(255,255,255,0.04);
    }

    .ticket-type {
      font-size: 10px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 2px;
      color: rgba(255,255,255,0.5);
    }
    .event-name {
      font-size: 28px;
      font-weight: 900;
      line-height: 1.1;
      margin-top: 8px;
    }
    .event-meta {
      display: flex;
      gap: 24px;
      margin-top: 12px;
    }
    .meta-item .label {
      font-size: 9px;
      text-transform: uppercase;
      letter-spacing: 1px;
      color: rgba(255,255,255,0.4);
      margin-bottom: 2px;
    }
    .meta-item .value { font-size: 13px; font-weight: 600; }

    /* Middle divider — perforated look */
    .divider {
      width: 2px;
      background: repeating-linear-gradient(
        to bottom,
        #333 0px, #333 8px,
        transparent 8px, transparent 14px
      );
      position: relative;
      flex-shrink: 0;
    }
    .divider::before, .divider::after {
      content: '';
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      width: 20px;
      height: 20px;
      background: white;
      border-radius: 50%;
    }
    .divider::before { top: -10px; }
    .divider::after { bottom: -10px; }

    /* Right panel — stub */
    .stub {
      width: 2.2in;
      background: white;
      padding: 0.35in 0.3in;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
    }
    .attendee-name { font-size: 15px; font-weight: 800; color: #111; }
    .seat-info { }
    .seat-label { font-size: 9px; text-transform: uppercase; letter-spacing: 1px; color: #999; margin-bottom: 2px; }
    .seat-value { font-size: 22px; font-weight: 900; color: #111; }

    .qr-placeholder {
      background: #f0f0f0;
      border: 1px solid #ddd;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 9px;
      color: #999;
      text-align: center;
      padding: 8px;
      border-radius: 4px;
    }
    .ticket-id {
      font-family: monospace;
      font-size: 9px;
      color: #999;
      letter-spacing: 1px;
      text-align: center;
      margin-top: 4px;
    }
  </style>
</head>
<body>
  <div class="main">
    <div>
      <div class="ticket-type">${ticketType}</div>
      <div class="event-name">${eventName}</div>
    </div>

    <div class="event-meta">
      <div class="meta-item">
        <div class="label">Date</div>
        <div class="value">${eventDate}</div>
      </div>
      <div class="meta-item">
        <div class="label">Time</div>
        <div class="value">${eventTime}</div>
      </div>
      <div class="meta-item">
        <div class="label">Doors</div>
        <div class="value">${doorTime}</div>
      </div>
    </div>

    <div>
      <div style="font-size:11px;color:rgba(255,255,255,0.5);margin-bottom:2px">${venue}</div>
      <div style="font-size:10px;color:rgba(255,255,255,0.35)">${venueAddress}</div>
    </div>
  </div>

  <div class="divider"></div>

  <div class="stub">
    <div>
      <div style="font-size:9px;text-transform:uppercase;letter-spacing:1px;color:#999;margin-bottom:2px">Attendee</div>
      <div class="attendee-name">${attendeeName}</div>
    </div>

    ${seatSection ? `
    <div class="seat-info">
      <div class="seat-label">Section / Row / Seat</div>
      <div class="seat-value">${seatSection} · ${seatRow} · ${seatNumber}</div>
    </div>` : ""}

    <div>
      <!-- In production: render a real QR code with a library like qrcode -->
      <div class="qr-placeholder" style="width:80px;height:80px;font-size:8px">
        QR<br>${ticketId}
      </div>
      <div class="ticket-id">${ticketId}</div>
    </div>
  </div>
</body>
</html>`;
}

Add a real QR code

import QRCode from "qrcode";

async function renderTicketWithQR(ticket) {
  // Generate QR code as data URL
  const qrDataUrl = await QRCode.toDataURL(
    `https://yourapp.com/tickets/verify/${ticket.ticketId}`,
    { width: 120, margin: 1 }
  );

  const html = renderEventTicketHtml({ ticket })
    .replace(
      `<!-- In production: render a real QR code with a library like qrcode -->
      <div class="qr-placeholder" style="width:80px;height:80px;font-size:8px">
        QR<br>${ticket.ticketId}
      </div>`,
      `<img src="${qrDataUrl}" width="80" height="80" style="border-radius:4px" />`
    );

  return html;
}

Generate PDF on booking confirmation

async function issueTicket(booking) {
  const ticketId = `TKT-${Date.now().toString(36).toUpperCase()}`;

  const html = await renderTicketWithQR({
    eventName: booking.event.name,
    eventDate: new Date(booking.event.startsAt).toLocaleDateString("en-US", {
      weekday: "long", month: "long", day: "numeric", year: "numeric",
    }),
    eventTime: new Date(booking.event.startsAt).toLocaleTimeString("en-US", {
      hour: "numeric", minute: "2-digit",
    }),
    doorTime: new Date(booking.event.doorsAt).toLocaleTimeString("en-US", {
      hour: "numeric", minute: "2-digit",
    }),
    venue: booking.event.venue.name,
    venueAddress: booking.event.venue.address,
    attendeeName: booking.attendee.name,
    ticketType: booking.ticketType,
    seatSection: booking.seat?.section,
    seatRow: booking.seat?.row,
    seatNumber: booking.seat?.number,
    ticketId,
    orderId: booking.orderId,
  });

  const res = await fetch("https://pagebolt.dev/api/v1/pdf", {
    method: "POST",
    headers: {
      "x-api-key": process.env.PAGEBOLT_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html }),
  });

  const pdf = Buffer.from(await res.arrayBuffer());

  // Store and email
  await uploadToS3(pdf, `tickets/${ticketId}.pdf`);
  await db.tickets.create({ data: { ticketId, bookingId: booking.id } });
  await emailTicket({ booking, pdf, ticketId });

  return { ticketId, pdf };
}

Stripe webhook handler

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), async (req, res) => {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers["stripe-signature"],
    process.env.STRIPE_WEBHOOK_SECRET
  );

  if (event.type === "payment_intent.succeeded") {
    const booking = await db.bookings.findOne({
      where: { stripePaymentIntentId: event.data.object.id },
      include: { event: { include: { venue: true } }, attendee: true, seat: true },
    });

    if (booking) await issueTicket(booking);
  }

  res.json({ received: true });
});

Multi-ticket order (batch)

async function issueOrderTickets(order) {
  const tickets = await Promise.all(
    order.bookings.map((booking) => issueTicket(booking))
  );

  // Combine all tickets into one PDF for download
  // (merge PDFs client-side or use a PDF merge library)
  return tickets;
}

The ticket renders at 8.5×3.5 inches — standard event ticket dimensions, printable on any laser or inkjet. QR code links to your own verification endpoint so scanning works offline.

Try it free — 100 requests/month, no credit card. → Get started in 2 minutes

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How to generate a PDF ticket or boarding pass automatically

Thematisch verwandte Begriffe: generate, ticket, boarding, pass · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61591 | djust provides Phoenix LiveView-style reactive server-side rendering for…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
News ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

↗ Original-Quelle