WordPress event tickets in Apple & Google Wallet
FooEvents, Tickera, Event Tickets, or any WordPress ticketing plugin: keep selling tickets the way you do, and give every buyer a wallet pass — one per ticket, scannable at the door by the plugin's own app or by any phone running Pass Studio's staff app, updated in place when plans change, and followed by a post-event pass that keeps the audience. No plugin to install: a short hook and an API key.
How it fits together
- Design the event pass once in the Studio: event name, venue, date, door time, and the per-ticket fields (seat, section, row, ticket holder) left editable per holder.
- The plugin sells the ticket exactly as today. When the ticket record exists, a hook calls issue once per ticket: attendee email, name, the ticket number as the pass barcode, the seat as a field. The attendee gets the "Add to Wallet" email.
- At the door, either scanner works. The plugin's check-in app reads the ticket number off the wallet pass like it reads the PDF; Pass Studio's staff app on any phone does too, and also marks the pass used in the wallet.
- Plans change, push once. New door time or venue on every pass in place, with a lock-screen notification.
- After the event, a journey turns the spent ticket into next season's pass or a loyalty card, so the audience stays in wallets.
FooEvents for WooCommerce — worked example
FooEvents publishes no developer hooks (its help center says so), but it stores each ticket as a post of type event_magic_tickets with documented meta keys: WooCommerceEventsTicketID, WooCommerceEventsAttendeeName, WooCommerceEventsAttendeeLastName, WooCommerceEventsAttendeeEmail, WooCommerceEventsOrderID, and WooCommerceEventsStatus. So the recipe keys off WooCommerce's order-completed hook, reads the order's tickets, and issues one pass each. If FooEvents has not created the tickets yet when the hook runs, the snippet retries once two minutes later.
<?php
// Pass Studio — one wallet pass per FooEvents ticket, issued when the order completes.
// wp-config.php: define( 'PASSSTUDIO_API_KEY', 'ps_live_...' );
// define( 'PASSSTUDIO_EVENT_PASS_ID', '<pass id from Pass Studio>' );
// FooEvents publishes no hooks, so this keys off WooCommerce and reads the ticket
// posts (post type event_magic_tickets) FooEvents creates for the order.
add_action( 'woocommerce_order_status_completed', 'passstudio_issue_event_passes', 99 );
add_action( 'passstudio_issue_event_passes_retry', 'passstudio_issue_event_passes' );
function passstudio_issue_event_passes( $order_id ) {
if ( ! defined( 'PASSSTUDIO_API_KEY' ) || ! defined( 'PASSSTUDIO_EVENT_PASS_ID' ) ) {
return;
}
$tickets = get_posts( array(
'post_type' => 'event_magic_tickets',
'post_status' => 'any',
'numberposts' => -1,
'meta_key' => 'WooCommerceEventsOrderID',
'meta_value' => (string) $order_id,
) );
if ( empty( $tickets ) ) {
// FooEvents may create the tickets after this hook runs — try once more in two minutes.
if ( ! wp_next_scheduled( 'passstudio_issue_event_passes_retry', array( $order_id ) ) ) {
wp_schedule_single_event( time() + 120, 'passstudio_issue_event_passes_retry', array( $order_id ) );
}
return;
}
foreach ( $tickets as $ticket ) {
$ticket_number = get_post_meta( $ticket->ID, 'WooCommerceEventsTicketID', true );
$email = get_post_meta( $ticket->ID, 'WooCommerceEventsAttendeeEmail', true );
$first = get_post_meta( $ticket->ID, 'WooCommerceEventsAttendeeName', true );
$last = get_post_meta( $ticket->ID, 'WooCommerceEventsAttendeeLastName', true );
if ( ! $ticket_number ) {
continue;
}
$response = wp_remote_post( 'https://www.passstudio.online/api/v1/passes/' . PASSSTUDIO_EVENT_PASS_ID . '/issue', array(
'timeout' => 15,
'headers' => array(
'Authorization' => 'Bearer ' . PASSSTUDIO_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'email' => $email, // the wallet link is emailed to the attendee
'name' => trim( $first . ' ' . $last ),
'barcodeValue' => (string) $ticket_number, // the plugin's scanner and ours read the same code
'fields' => array( // per-holder values on your event pass
'ticketHolder' => trim( $first . ' ' . $last ),
),
'clientRequestId' => 'fooevents-' . $ticket->ID, // safe to re-run: same ticket, same pass
) ),
) );
if ( is_wp_error( $response ) ) {
error_log( 'Pass Studio issue failed for ticket ' . $ticket->ID . ': ' . $response->get_error_message() );
}
}
}- Field keys are yours.
ticketHolderabove is the event pass's field; list a pass's editable keys with GET /passes and map seat, section, or row the same way when the plugin stores them (FooEvents seating and custom attendee fields are additional meta keys on the same post). - Re-runs are safe.
clientRequestIdper ticket plus the attendee email make the issue call idempotent: a hook that fires twice returns the same pass. - Which code goes on the barcode. FooEvents' check-in app scans its ticket QR; put the value that app reads on the pass (the ticket ID shown here, or the ticket hash if your setup scans that) so one code works in both scanners.
- One pass design per event (the pass id in
wp-config.php), or per event type if your dates live in fields — a small mapping from WooCommerce product id to Pass Studio pass id replaces the constant when you run many events at once.
Tickera
Tickera fires tc_created_order_ticket_instance after each ticket instance is created (added in 1.6.6). The pattern is the same: read the instance's ticket code and the buyer, issue one pass with the code as barcodeValue. Verify the hook's argument and the meta key names against your installed Tickera version before relying on them.
<?php
// Tickera fires tc_created_order_ticket_instance after each ticket instance is created
// (Tickera 1.6.6 and later). Check the argument your Tickera version passes — it is the
// ticket instance (post) id in current releases — then issue exactly as above, with the
// instance's ticket code as barcodeValue and the buyer's email from the order.
add_action( 'tc_created_order_ticket_instance', function ( $ticket_instance_id ) {
$code = get_post_meta( $ticket_instance_id, 'ticket_code', true );
$order = wc_get_order( wp_get_post_parent_id( $ticket_instance_id ) );
if ( ! $code || ! $order ) {
return;
}
// ... same wp_remote_post( '/api/v1/passes/{passId}/issue' ) call as the FooEvents example,
// with 'barcodeValue' => $code and 'clientRequestId' => 'tickera-' . $ticket_instance_id
} );Event Tickets, WP Event Manager, and others
Any plugin that exposes a ticket-created hook or a ticket record you can query by order follows the same three lines: find the ticket, take its number, call issue with the buyer's email. No hook at all? Put the pass's share link or QR in the confirmation email for general-admission events, or let Zapier do it: a WooCommerce "New Order" trigger into Pass Studio's Issue Pass by Email action, no code.
At the door
- Plugin scanner: scans the ticket number off the wallet pass; the plugin's attendance stays the source of truth.
- Pass Studio staff app: any phone, no hardware; a scan calls redeem, marks the pass used, and fires
pass.redeemedso your systems (or a Zap) can record attendance. Set the pass to one use for single-entry events, or unlimited with a fresh face per day for multi-day festivals. - Google Wallet Smart Tap for NFC entry is available on the events track — ask us if your gates have NFC readers.
FAQ
Which WordPress event plugins does this work with?
Any plugin that creates a ticket record you can read after purchase. The worked example is FooEvents for WooCommerce, whose ticket posts and meta keys are documented; Tickera has a ticket-creation hook since 1.6.6; Event Tickets, WP Event Manager, and others follow the same pattern: when the ticket exists, issue one pass with the ticket number as its barcode.
Does this replace the plugin's own check-in app?
No, it sits beside it. Put the plugin's ticket number (or hash — whichever its scanner reads) on the pass barcode and the plugin's check-in app scans the wallet pass exactly like its PDF. Or check in with Pass Studio's staff app on any phone, which also marks the pass used in the wallet. Either scanner, one code.
What happens when the event changes?
Push the change once — new door time, new venue, a lock-screen message — and every issued pass updates in place. No resending tickets.
What does it cost?
One credit per pass issued, one per push update to all holders, one per door scan (per register window on subscription plans). Idle tickets cost nothing between purchase and the event.
Is there a WordPress plugin to install?
No. WordPress runs on your server, so the integration is a short snippet in your theme or a code-snippets plugin plus an API key kept in wp-config.php. Nothing from us to install or keep updated.
Can guests get a pass without a hook at all?
Yes. Put the pass's share link or QR in the ticket confirmation email or on the thank-you page; the guest saves the pass themselves. You lose the per-ticket barcode mapping, so use this for general-admission events where the pass is the credential.
Related
- Pass Studio for events — the whole event lifecycle in wallets.
- WooCommerce integration — coupons, loyalty, and memberships for the same store.
- API and Integration Guide — issue, fields, redeem, webhooks.