Build a flow

Eventlayer / Build a flow

Issue tickets after payment

Connect a verified payment to ticket issuance without treating the browser's success page as proof of purchase.

What you will build

Use your existing checkout and payment provider. Eventlayer handles the ticket after your backend confirms the order is paid; it does not charge the customer, calculate tax, or process refunds.

The flow you will build
  1. 01Verified payment
  2. 02Saved order
  3. 03Assigned ticket
  4. 04Confirmation

Prerequisites

  • A payment provider with a test environment and signed server notifications.
  • An Eventlayer event and ticket type, plus your server-side API key.
  • A durable order database and job worker. Start with one admission per order.

1. Map your checkout product to a ticket type

Configure the event and ticket type in the Eventlayer dashboard. In your own database, map your checkout product to their IDs and your price/currency. Persist the buyer details, order ID, and expected amount before checkout.

Use that server-side mapping when issuing access. Never trust an event ID, ticket tier, quantity, or price supplied solely by the browser.

2. Accept the payment provider's notification

Implement your provider's webhook verification using its current official SDK and documentation. This is a payment-provider webhook, not an Eventlayer webhook; the Eventlayer signing secret cannot verify it.

After verification, locate the order and check that the amount, currency, product, and final payment state match your requirements. A checkout redirect, pending payment, or authorization alone is not proof of a settled purchase.

Persist a unique payment-event record and enqueue fulfillment atomically. Acknowledge only after saving it; repeated provider notifications should reuse the same order. Your success page can show "Preparing your ticket" until the worker completes.

3. Issue the admission from your worker

Use the createAttendee and issueTicket functions from the website registration guide. The following is workflow pseudocode, not a built-in database or payment integration:

Lock fulfillment for this order
  If the order is not confirmed paid: stop
  If a ticket ID is already saved: return the existing ticket
  If an earlier API call has an unknown outcome: reconcile before continuing
  If no attendee ID is saved:
    Create the attendee and save the returned ID
  Create the ticket with that attendee ID and the mapped ticket type
  Save the ticket ID and mark fulfillment complete
  Enqueue the confirmation email once
Release the lock

Keep automatic SDK retries disabled for creation. If a call times out after Eventlayer writes the record, a retry could issue another admission. Record an "unknown outcome" for manual or automated reconciliation; do not blindly call create again. Saving progress and locking orders prevents ordinary duplicate work, but cannot make your database and the remote API one transaction.

For multiple admissions, create one durable fulfillment line per ticket with its own state. Decide whether the buyer names every attendee or assigns them later; don't silently issue all access to one person.

4. Deliver the ticket

Show the fulfilled ticket on an authorized order-confirmation page. Add an Apple Wallet download and use your email service to deliver a link to that page.

If payment succeeded but ticket creation failed, keep the paid order visible to your operations team. Retry only known-safe steps or reconcile; don't ask the customer to pay again to recover a ticket.

5. Coordinate refunds and access removal

A refund does not automatically change Eventlayer access. Decide when your refund policy requires revocation and enqueue a separate, auditable operation against the order's saved ticket ID.

The current SDK exposes ticket deletion, not a cancel() method. The following removes a ticket; it is not a dry run or a reversible refund action:

payments.ts · server only
import { Eventlayer } from "eventlayer";

const eventlayer = new Eventlayer(process.env.EVENTLAYER_KEY!, { maxRetries: 0 });

// Call only after your backend authorizes revocation for this order.
export async function removeOrderTicket(ticketId: string): Promise<void> {
  const { error } = await eventlayer.tickets.delete({ ticketId: ticketId });
  if (error) throw error;
}

Keep the order and audit trail in your database. If a device is registered for the ticket's Wallet pass, Eventlayer retains a cancelled record so the installed pass can be updated; otherwise the ticket is deleted. An installed pass cannot be remotely removed from the attendee's device. Do not assume the ticket will remain queryable after deletion or that a refund reverses historical attendance.

Test and recover

  1. Pay for one admission in your provider's test environment. Confirm one assigned ticket.
  2. Deliver the same payment notification twice, including concurrently. Confirm no extra ticket.
  3. Visit the success URL without paying. No admission should be issued.
  4. Send an invalid signature, wrong currency, or incomplete payment. Reject fulfillment.
  5. Interrupt fulfillment after attendee creation; resume using the saved attendee ID.
  6. Test an ambiguous API timeout and verify it enters reconciliation, not blind retry.
  7. Refund a disposable test order and run your revocation workflow. Verify the old ticket cannot be checked in.

Next steps

Connect Eventlayer's own ticket webhooks to your CRM to track the attendee lifecycle separately from your payment ledger.