Event Reference

Every event Pass Studio records across the life of a pass, the fields each one carries, and the name it has in each place you can receive it. Schema v1, September 2026.

The lifecycle

A pass is viewed when someone opens its share page, installed when it lands in a wallet, redeemed when its code is used on any channel, and removed when the holder deletes it or you revoke it. Loyalty passes add earned and reward unlocked between install and redemption. A pass with a journey adds a transition after redemption, when it transforms into the next step instead of going gray.

Every event is written once, to one stream, and the same record is what feeds the Analytics dashboard, your webhook endpoints, your Segment workspace, and your Zaps. The names differ by channel because each follows its own convention; the table below is the map between them.

Events

EventFires whenWebhookSegmentZapier
viewedA share page is opened in a browser, before any wallet action.—Pass Viewed—
downloadedA pass is added to Apple Wallet or Google Wallet — from a share page, an installation email, a form, an API issue call, or a connected store.pass.issuedPass InstalledPass Issued
redownloadedA holder who already has the pass adds it again.—Pass Reinstalled—
redeemedA pass is redeemed on any channel: online checkout with the pass code, a Square register, a POS counter scan, the API, or a loyalty reward being used.pass.redeemedPass RedeemedPass Redeemed
removedA holder deletes the pass from their wallet, or you revoke it.pass.removedPass RemovedPass Removed
loyalty_issuedA loyalty pass is issued to a customer by the loyalty loop.—Loyalty Pass Issued—
loyalty_earnedPoints are earned on a loyalty pass. Since 2026-10-29 this includes the award that reaches the reward threshold, published right before loyalty_reward_unlocked (before that date the threshold award published only loyalty_reward_unlocked).holder.points_changedLoyalty Points EarnedHolder Points Changed
loyalty_reward_unlockedA points balance crosses a reward threshold.holder.reward_unlockedLoyalty Reward UnlockedHolder Reward Unlocked
loyalty_redeemedA loyalty reward is used. One row per redemption order; it also carries the points that order earned (earned, balance). Since 2026-10-29 those points fire holder.points_changed (delta = earned, reason "reward"); a redemption that earned nothing fires no points event.pass.redeemed; holder.points_changed when the order earned points (since 2026-10-29)Pass Redeemed, Loyalty Points EarnedPass Redeemed; Holder Points Changed when the order earned points
journey_transitionA holder's card hops to the next pass of its journey — one row per hop of a run (a cascade publishes one per pass passed through).pass.journey_advancedPass Journey AdvancedPass Journey Advanced
order.processedAn order is fed through the order engine, from a connected store or the API.—Order Completed—
visitedA membership card scan is counted as a visit (one per holder per 60 minutes).holder.visitedMember VisitedHolder Visited
member_updatedStaff edit or award something on one member's card.holder.updated, holder.points_changedMember UpdatedHolder Updated, Holder Points Changed
tier_changedA holder's status level in a points bank changes: up at the award that crosses a ladder threshold, down at the bank's period cutoff (metadata: direction, level, previousLevel, levelName, previousLevelName, plus the legacy tierIndex, tierTitle, previousTierIndex).holder.tier_changedLoyalty Tier Changed—

visited and member_updated belong to membership cards and surface on the member's timeline in the dashboard. Since 2026-10-24 both reach Segment, as Member Visited and Member Updated (the Segment page states what each carries in each privacy mode), and both fire a webhook — holder.visited, and holder.updated with every changed field's before and after values and the staff note in full — with the Zapier triggers Holder Visited and Holder Updated (app 1.0.19). A member_updated that changes points on a membership card with no loyalty program also fires holder.points_changed; a program-backed award fires it from loyalty_earned instead, so one award is one points event. Shopify Flow receives them as Visit counted and Points changed.

Webhook-only events

Two state changes are delivered to webhook endpoints but have no analytics row of their own:

WebhookFires whenZapier
pass.updatedField values or the barcode change on a pass or on one holder's instance, from Studio or the API.—
holder.mergedTwo holder profiles are identified as the same customer and consolidated.Holder Merged

Wallet plumbing

Apple Wallet also produces register, unregister, and fetch events as devices subscribe to updates and pull new versions of a pass. They are recorded for operations and support and are deliberately not forwarded to any channel: they would be noise in a marketing workspace. The install signal you want is downloaded.

Fields

Every event carries the same core fields. Not every field is set on every event.

FieldMeaning
passIdThe pass the event belongs to.
platformapple, google, or web (web = browser-only events such as viewed).
timestampUTC, ISO 8601.
country, region, cityDerived from the request IP at the time of the event. Never stored on the holder.
userAgentThe browser or wallet client that made the request.
shareTokenThe share link the holder came through, when there was one.
utmSource, utmMedium, utmCampaignThe campaign parameters on this event's own touch. On counter and order redemptions the medium is pos or order and the campaign names the register location or the order (order_<number>); the source is the channel: pass-studio_shopify, pass-studio_api, pass-studio_square, pass-studio_clover, or the register.
utmContent, utmTermutm_content and utm_term on this event's own touch; set on viewed and downloaded from the landing URL. Not stamped as first touch, so absent on later events.
acqUtmSource, acqUtmMedium, acqUtmCampaignFirst-touch acquisition: the campaign that produced the install, carried onto every later event for the same instance so a redemption can be attributed to the click that started it.
acqPartnerId, acqClickIdPartner attribution from the acquiring link (pid / cid), stamped at install and carried onto later events. See Partner attribution & postbacks.
serialNumberThe wallet serial of the instance, where the event is instance-scoped.
metadataPer-event detail, listed below.

Per-event metadata

EventKeys
downloadedinstanceId, serial (the card), holderId (the holder, when the identity engine resolved one at install), via (for example api_issue), viewport, language, and query — the full landing query string, first 1,000 characters, every parameter the link carried, for lossless attribution.
redeemedinstanceId plus locationId for counter scans; shopifyOrderId and discountCode for store orders, plus holderId and serial when the code is one holder's own — a unique- or pool-mode code, a membership card's or a loyalty card's own code — on every channel (store order, counter scan, Square sale, API redemption; the scan channels since 2026-10-24); on a code shared by every holder of a simple pass they are set only when the order email matches exactly one active card of that pass (since 2026-10-24), otherwise absent.
loyalty_issued, loyalty_earned, loyalty_reward_unlocked, loyalty_redeemedprogramId, instanceId (the card, since 2026-10-24), holderId (the holder), loyaltyHolderId (the program member record), serial (the card), code, earned, balance; shopifyOrderId on order-driven events (the Shopify order id, or owner_source_externalOrderId for an order sent through the API; an award with no order carries "0" or none), rewardCode on loyalty_reward_unlocked, delta and manualAdjust on a points adjustment, reason when the caller gave one; thresholdReached: true on the loyalty_earned row published together with a loyalty_reward_unlocked (since 2026-10-29).
tier_changedinstanceId, holderId, loyaltyHolderId, serial, programId, direction, level and previousLevel (letters A–E, since 2026-10-26), levelName and previousLevelName (the pass's names), workspaceId, balanceAfter, earnedCurrent; tierIndex, tierTitle and previousTierIndex are kept (index = the letter's position, title = the name); block and blockEarned only on the legacy path.
earning cards (since 2026-10-27)A card that earns by itself (the earning block, no program) publishes loyalty_earned with earningCard: true, reason "order" | "welcome" | "scan", delta, balance and the points-bank fields, no programId; a balance step that lands a card on a reward pass publishes loyalty_reward_unlocked with earningCard: true, rewardPassId, rewardCode, cost and runId. The webhooks (holder.points_changed, holder.reward_unlocked) and Zapier's triggers fire from them as from a program.
points-bank fields (since 2026-10-26)Every loyalty_earned, loyalty_redeemed, loyalty_reward_unlocked and member_updated row of a workspace on a points bank also carries workspaceId (where the movement happened), balanceAfter, earnedCurrent (earned this period) and level (the holder's status letter after the movement). Absent until the workspace is migrated.
journey_transitionfromPassId, toPassId, instanceId, source (issue, scan, order, time, tier, api), runId (shared by every hop of one run, since 2026-10-26), cascade (true on every hop after the first), final (true on the last hop of the run), hopIndex and when (the step that fired), plus holderId (the holder) and serial (the card) when the card that moved is one holder's own — since 2026-10-24; a legacy shared card of a simple pass carries neither. The first-touch acquisition columns (acqUtm*, acqPartnerId, acqClickId) are set on the same event when the card carries a stamp.
order.processedsource (shopify, api, square, clover, or the API caller's own source name), orderRef (Pass Studio's order reference, unique per workspace and order), orderNumber, action (the engine's outcome: issued, redeemed, loyalty_issued, loyalty_points_earned, loyalty_reward_unlocked, membership_invite_sent, membership_already_member, no_pass_configured, insufficient_tokens, and the other outcomes the order endpoint documents), issuedPassIds[] (when one order issued several passes), subtotal, total, currency, discountCodes[] (every code on the order), items[] (one per line item: code = SKU, description, quantity, price), plus holderId and serial when the order reached one holder's own card (their own code redeemed, a loyalty order, an auto-issued card, an existing member's order). An order that issued nothing carries neither; an order paid with a simple pass's shared code carries them only when the order email matches exactly one active card of that pass (since 2026-10-24), otherwise neither. No personal data: no email, name or address. The first-touch acquisition columns (acqUtm*, acqPartnerId, acqClickId) are set under the same rule. This is the replayable order stream.
visitedinstanceId, registerId, registerName, visitCount (the holder's counted visits including this one), holderId (the holder) and serial (the card). registerName, visitCount and serial since 2026-10-24; the first-touch acquisition columns (acqUtm*, acqPartnerId, acqClickId) are set since the same date.
member_updatedinstanceId, changes[{key, from, to}] (values as strings), note (up to 280 characters), source (studio or api; absent on a staff-app change), registerId and registerName (staff app; registerId also on an API call that reported one), actorUid (a Studio change) or keyId (an API change), engineHandled: true when a program-backed award also produced loyalty_earned, holderId (the holder) and serial (the card). serial, registerName and the acquisition columns since 2026-10-24. The analytics row holds every value in every mode; only the Segment copy withholds person-level values outside Full identity mode.

Webhook payloads add the holder's identity where known (email, phone, external IDs such as a Shopify customer ID). Segment receives a fixed set of these fields under Segment's property names, with the holder as userId; the complete property-by-property list, the identity rules and example payloads are on the Segment page.

Getting events out

  • Signed webhooks — real-time push to your own endpoint, HMAC-signed, queued with retries, identity-enriched. Best for CRM sync and anything that must react within seconds. Webhooks reference →
  • Segment — every event as a Segment track call with first-touch attribution, and from there to GA4, Mixpanel, Amplitude, Adobe Analytics, or a warehouse. Best for analytics and attribution. Segment integration →
  • Zapier — eleven triggers with no code: issued, updated, redeemed, removed, holder merged, tier changed, points changed, reward unlocked, journey advanced, visited and holder updated. Best for spreadsheets, email tools, and one-off automations. Zapier integration →
  • REST API — pull the current state rather than the event stream: a holder's passes and points, a pass's instances, field values. Best for lookups at the moment you need them. API reference →
  • Analytics dashboard — views, installs, redemptions, and removals per pass, by platform and campaign, with no setup.

Landing events in your warehouse

Connect Segment and add a warehouse destination (BigQuery, Snowflake, Redshift, Postgres, or Databricks). Segment creates one table per event using its snake-case convention, so Pass Installed becomes pass_installed and Pass Redeemed becomes pass_redeemed, each with the fields above as columns. A redemption joined to its install on instanceId or the acquisition fields gives you click-to-redemption attribution in plain SQL. For a lower-volume alternative, point a webhook endpoint at a function that writes rows directly.

Privacy

Location fields are derived from the request IP at event time and are never written back to the holder. Individual events are kept for a bounded period and then aggregated; redaction requests remove holder identity from the records they cover. Retention periods, redaction behavior, and where the data lives are on the Security & data page.

Versioning

This is schema v1. Changes are additive: new events and new fields may appear, existing names and meanings do not change. A breaking change would ship as a new version with notice on this page.

Changelog

  • September 2026 — v1 published: the events, fields, and channel names documented on this page.
  • 2026-10-23 — additive: order.processed carries holderId and serial in metadata and the acqUtm* / acqPartnerId / acqClickId columns when the order reached one holder's own card; redeemed from an online order carries holderId and serial under the same rule; every loyalty event and tier_changed carry holderId (the holder) next to loyaltyHolderId (the program member record) and serial; utmContent and utmTerm are forwarded to Segment. No existing name or meaning changed.
  • 2026-10-24 — additive: journey_transition carries holderId and serial in metadata and the acqUtm* / acqPartnerId / acqClickId columns when the card that moved is one holder's own; the six tier details on tier_changed (direction, tierIndex, tierTitle, previousTierIndex, block, blockEarned) are forwarded to Segment as properties of Loyalty Tier Changed; visited and member_updated carry holderId, serial and the acquisition columns, visited adds registerName and visitCount, member_updated from the staff app adds registerName, and both are forwarded to Segment as Member Visited and Member Updated. Five webhook events are mapped from this stream — holder.points_changed (from loyalty_earned, and from member_updated when it changes points on a membership card with no loyalty program), holder.reward_unlocked, pass.journey_advanced, holder.visited, holder.updated — each with a Zapier trigger (app 1.0.19); the four loyalty events carry instanceId in metadata. No existing name or meaning changed. On a shared simple-mode code, redeemed and order.processed carry holderId, serial and the acquisition columns only when the order email matches exactly one active card of the pass; otherwise none, never another holder's.
  • 2026-10-29 — additive: the award that reaches the reward threshold now publishes loyalty_earned (with thresholdReached: true) right before loyalty_reward_unlocked, so holder.points_changed, Shopify Flow's Points changed, Segment's Loyalty Points Earned and the loyalty activity report's Points earned total see every balance change. Before this date that award produced only loyalty_reward_unlocked; for older rows its earned and balance fields hold the change. A redemption order is unchanged in the stream: one loyalty_redeemed row; since the same date that row also fires holder.points_changed for the points the order earned (delta = earned, reason "reward", the order id), as Shopify Flow's Points changed already read it; a redemption that earned nothing fires no points event. From the same date a manual award with no order (API, Studio, staff app, campaign bonus) on a program card past the welcome state carries delta, manualAdjust: true and the caller's reason and no shopifyOrderId, as the welcome-state and deduction path always did; before, such an award ran as a synthetic order and carried the claim key <team>_api_points_<holder> as shopifyOrderId with no reason, so holder.points_changed and Flow's Points changed read reason "order". No existing name or meaning changed.

Questions?

Email support@thepassstudio.com or use the assistant in the bottom-right corner of the dashboard.