Segment Integration
Pass Studio is a Segment source. Every pass event is sent to your Segment workspace as a track call, with the holder as userId and the campaign that acquired the holder on every later event, and from there to Google Analytics 4, Mixpanel, Amplitude, Adobe Analytics, Braze, Iterable, Klaviyo, HubSpot, Mailchimp, Customer.io, a data warehouse (BigQuery, Snowflake, Redshift, Postgres, Databricks), and the other destinations in Segment's catalog. This page lists every event, every property, every identity rule, and what is never sent. Last updated 2026-10-24.
What this is
Pass Studio records one event for each thing that happens to a pass: a share page viewed, a card installed, a code redeemed, a loyalty balance changed, an order processed. Each event is written to Pass Studio's own analytics first, then forwarded to your Segment source as one track call to https://api.segment.io/v1/track, authenticated with your write key, with a 3-second timeout. Forwarding is best-effort: a failed call is recorded as the connection's last error, is not retried, and never affects a pass, a redemption, or the data inside Pass Studio. There is no Segment catalog app to install and no approval step; the source is a self-serve HTTP API source in your own workspace.
Setup, step by step
- In Segment, open Connections → Sources → Add source, choose HTTP API, name it (for example "Pass Studio"), and create it. The HTTP API source is on every Segment plan, including the free one.
- On the new source, open Settings → API Keys and copy the Write Key.
- In Pass Studio, open Settings → Integrations → Segment. If you work in teams, first pick the workspace in the team switcher: the connection belongs to the workspace that is active when you save it.
- Paste the write key and click Connect. Pass Studio stores the key and shows it masked.
- Set the Customer profiles privacy sheet (see below): pick a preset or check the boxes yourself. The default is Anonymous personas.
- Optionally list Link parameters (for example gclid, fbclid, a promo code key). Each one is forwarded under its own name on views and installs; a parameter you do not list is never sent.
- Click Send test event. Within a few seconds the Debugger tab of your Segment source shows a track call named
Pass Studio Test EventwithanonymousIdtest:<your workspace id>and one property,source: "settings-test-button".
From that moment every new event is forwarded. Events from before the connection are not replayed. Destinations you have wired in Segment (analytics, marketing automation, a warehouse) receive the events through your own Segment routing and consent settings; Pass Studio sends to the source only.
The card in Settings shows the connection's last successful send and its most recent error. Settings changes (mode, write key, disconnect) take effect within 60 seconds.
Events
Fourteen event names. The table gives, for each, the exact trigger, the complete list of properties it can carry, and the identity it is sent under. A property is present only when its value is known; absent properties are omitted, never sent as null. The same events, with their warehouse, webhook and Zapier names, are in the Event Reference.
| Event | Fires when | Properties | Sent as |
|---|---|---|---|
| Pass Viewed | A share page is opened in a browser, before any wallet action. | passId, platform ("web"), shareToken, utmSource, utmMedium, utmCampaign, utmContent, utmTerm | anonymousId = the share token (the link, never a person) |
| Pass Installed | A card is added to Apple Wallet or Google Wallet: from a share page, an install email, a sign-up form, an API issue call, or a connected store. | passId, platform ("apple" | "google"), shareToken (share-link installs), utmSource, utmMedium, utmCampaign, utmContent, utmTerm (the landing URL), acquisitionPartner, acquisitionClickId (when the link carried pid / cid) | userId = holder ID when the install identified a holder; anonymousId = the card's serial |
| Pass Reinstalled | A holder who already has the card adds it again. | Same as Pass Installed; the utm fields describe the link used for this re-add. | Same as Pass Installed |
| Pass Removed | A holder deletes the card from the wallet, or the merchant revokes it. | passId, platform | userId = holder ID and anonymousId = serial when known; otherwise pass:<passId> |
| Pass Redeemed | A code is used on any channel: an online order with the card's code, a Square register sale, a POS counter scan, an API redemption, or a loyalty reward being used. | passId, platform, utmSource, utmMedium, utmCampaign (the redeeming channel), acquisitionSource, acquisitionMedium, acquisitionCampaign, acquisitionPartner, acquisitionClickId (the card's first touch), source ("api", "square", or the register name on scans), orderId (API redemptions that report one), locationId (Square and counter scans), discountCode (store orders; the item code on Square), sku (Square), loyalty: true (a loyalty reward was used) | userId = holder ID and anonymousId = serial when an online order used one holder's own code (unique or pool mode), when a shared simple-mode code is resolved to the buyer's own card by the order email (exactly one active card of that pass with that email; none or several → anonymousId = pass:<passId> with no acquisition fields), and when a loyalty reward is used (loyalty: true). A staff-app counter scan, a Square register sale and an API redemption (POST /v1/redeem, POST /v1/loyalty/redeem) name the holder too, since 2026-10-24, when the scanned code is one holder's own: a unique- or pool-mode code, a membership card, a loyalty card. A shared simple-mode coupon scanned at a counter is sent on anonymousId = pass:<passId> with its acquisition fields. |
| Order Completed | Any paid order the order engine processes: a Shopify orders/paid webhook, a Square or Clover sale, or POST /v1/orders/process. Fires once per order, whatever the outcome, including orders that issued nothing. | passId (empty when nothing was issued), platform ("web"), utmSource ("pass-studio_shopify", "pass-studio_api", "pass-studio_square", "pass-studio_clover"), utmMedium ("order"), utmCampaign ("order_<number>"), acquisitionSource, acquisitionMedium, acquisitionCampaign, acquisitionPartner, acquisitionClickId (the holder's first touch, when the order reached one holder's own card), order_id, total, currency, products[] (name, sku, quantity, price), coupon | userId = holder ID and anonymousId = 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 → anonymousId = pass:<passId>; an order paid with a shared simple-mode code names the holder only when the order email matches exactly one active card of that pass , otherwise anonymousId = pass:<passId> with no acquisition fields |
| Loyalty Pass Issued | The loyalty loop enrolls a customer and mints their card, on their first paid order. | passId, platform ("apple"), utmSource, utmMedium ("order"), utmCampaign ("order_<number>"), programId. No acquisition fields: the enrolling order is this card's first touch and sits in the utm fields. | userId = holder ID; anonymousId = serial |
| Loyalty Points Earned | Points are added to a loyalty card: an order, a code-less order from a known member, a manual award, or an API or Zapier adjustment. | passId, platform ("apple"), utmSource, utmMedium, utmCampaign (the order), programId, earned, balance. acquisitionSource / Medium / Campaign / Partner / ClickId are present only when the same order also used a reward code. | userId = holder ID; anonymousId = serial |
| Loyalty Reward Unlocked | A balance crosses the program's reward threshold and a reward code is minted. | Same as Loyalty Points Earned. | userId = holder ID; anonymousId = serial |
| Loyalty Tier Changed | A member moves to another tier: up at the award that crosses the bar, down at a period boundary. | passId, platform ("web"), programId (absent on a membership card whose tiers are set on the pass itself, with no loyalty program), direction ("up" | "down"), tierIndex, tierTitle, previousTierIndex (absent on the member's first tier change), block, blockEarned. The six tier properties are forwarded since 2026-10-24; the holder.tier_changed webhook carries the same six plus unit, length and the tier's since timestamp. | userId = holder ID; anonymousId = serial |
| Pass Journey Advanced | A card moves to the next step of its pass journey, after a redemption, an order, a time rule, or a manual move. | passId (the step the card left), platform ("web"), fromPassId, toPassId, source ("scan" = a scan at the staff app or a redemption over POST /v1/redeem, "order" = a paid order, "time" = the afterDays rule, "heal" = a missed move repaired, "api" = the journeyMoveToNext action), acquisitionSource, acquisitionMedium, acquisitionCampaign, acquisitionPartner, acquisitionClickId (the card's first touch, when the card carries one) | userId = holder ID and anonymousId = serial when the card that moved is one holder's own (every card minted per wallet add, every unique or pool code); a legacy shared card of a simple pass carries neither → anonymousId = pass:<passId>. Since 2026-10-24; earlier events sit on pass:<passId> |
| Member Visited | A staff-app scan of a membership card is counted as a visit: at most one per card per 60 minutes. A rescan inside the window counts nothing and sends nothing. Forwarded since 2026-10-24. | passId, platform ("web"), acquisitionSource, acquisitionMedium, acquisitionCampaign, acquisitionPartner, acquisitionClickId (the card's first touch, when the card carries one), registerId, registerName, visitCount (the holder's counted visits including this one) | userId = holder ID; anonymousId = serial (a membership card is one holder's own) |
| Member Updated | Staff change something on one member's card: a points award or deduction at the staff app, in the Studio Holders panel or over the API, a tier move on a card with tiers, a member field correction. A program-backed points award also produces Loyalty Points Earned; Member Updated is the ledger entry, with the register and the note. Forwarded since 2026-10-24. | passId, platform ("web"), the five acquisition properties (when the card carries a first touch), changedFields[], changes[] ({ key, from, to } when the privacy sheet allows that field's values — its Core class box or its General box — else { key } alone), pointsFrom, pointsTo, pointsDelta (when points changed), tier, previousTier (when the tier moved), note (when Staff notes is on), source ("studio", "api", "register"), registerId, registerName (staff-app changes) | userId = holder ID; anonymousId = serial |
Not forwarded, on purpose: the wallet plumbing events register, unregister and fetch (devices subscribing to updates and pulling new versions), the daily health_check, and the membership counter events visited and member_updated. They would be noise in a marketing workspace. The install signal is Pass Installed.
Properties reference
Every property that can appear on any event, with its type, the events that carry it, where the value comes from, and an example. Property names are camelCase except the five Order Completed fields, which follow Segment's e-commerce spec (order_id, total, currency, products, coupon) so revenue-aware destinations recognize them without mapping.
| Property | Type | On | Where the value comes from | Example |
|---|---|---|---|---|
| passId | string | every event | The pass the event belongs to. Empty on an Order Completed that issued nothing. | dqSZHxwuVVGjAtEY6P7o |
| platform | "apple" | "google" | "web" | every event | The wallet, or "web" for browser and order events. | apple |
| shareToken | string | Pass Viewed, Pass Installed, Pass Reinstalled | The share link the person came through. Absent when the card came from an email, a form, the API or a store. | JSB11lYjQiAgGlD_j9GU9w |
| utmSource | string | every event that has one | utm_source on the landing URL (views, installs); on later events the channel name: pass-studio_shopify, pass-studio_api, pass-studio_square, pass-studio_clover, or the register. | meta |
| utmMedium | string | every event that has one | utm_medium on the landing URL; "order" on order-driven events; "pos" on counter scans. | cpc |
| utmCampaign | string | every event that has one | utm_campaign on the landing URL; "order_<number>" on order-driven events; the register location on counter scans. | spring_sale |
| utmContent | string | Pass Viewed, Pass Installed, Pass Reinstalled | utm_content on the landing URL. Forwarded since 2026-10-23. Not stamped as first touch, so absent on later events. | video_b |
| utmTerm | string | Pass Viewed, Pass Installed, Pass Reinstalled | utm_term on the landing URL. Forwarded since 2026-10-23. Not stamped as first touch, so absent on later events. | k1 |
| <allowed link parameter> | string | Pass Viewed, Pass Installed, Pass Reinstalled, and every later event of a card created on or after 2026-10-24 | A parameter of the landing URL that you listed under Settings → Integrations → Segment → Link parameters (gclid, fbclid, msclkid, a promo code key, anything), forwarded under its own name with its value. On a view or install it is the parameter on that link; on every later event of the card (Pass Redeemed, Order Completed, the loyalty and journey events) it is the parameter on the link that acquired the card, kept from the moment the card was created. Since 2026-10-24. A parameter you did not list is never sent. | gclid: CjwKCAjw… |
| acquisitionSource | string | Pass Redeemed, Order Completed, Pass Journey Advanced, Loyalty Points Earned and Loyalty Reward Unlocked on a reward order | utm_source of the link that acquired this card, stamped once when the card was created and never changed. On a card issued by an order, the API or a form it is the channel name (pass-studio_shopify, pass-studio_api, studio, pos-register). | meta |
| acquisitionMedium | string | same as acquisitionSource | utm_medium of the acquiring link, or the channel's medium ("order", "issue", "email", "add-member", "csv-import", "enroll"). | cpc |
| acquisitionCampaign | string | same as acquisitionSource | utm_campaign of the acquiring link. Absent when the acquiring link had none. | spring_sale |
| acquisitionPartner | string | Pass Installed and every later event of the card | The pid parameter on the acquiring link (partner attribution). | agency-north |
| acquisitionClickId | string | Pass Installed and every later event of the card | The cid parameter on the acquiring link (Pass Studio's own click id for partner postbacks; gclid and fbclid are separate and forwarded only when you list them under Link parameters). | c_8f3a1 |
| source | string | Pass Redeemed, Pass Journey Advanced, Member Updated | On Pass Redeemed the redeeming channel ("api", "square", the register name); on Pass Journey Advanced what moved the card: "scan", "order", "time", "heal" or "api"; on Member Updated who made the change: "studio" (the Holders panel), "api" (a /v1 call), "register" (the staff app). | square |
| orderId | string | Pass Redeemed from the API | The order reference the API caller sent with the redemption. | INV-1042 |
| locationId | string | Pass Redeemed at a Square register or a POS counter | The Square location id, or the counter register's id. | L8K2M9PQRST |
| discountCode | string | Pass Redeemed on a store order or a Square sale | The code the customer used; on Square, the item code that identified the card. | 1005 |
| sku | string | Pass Redeemed at a Square register | The Square catalog item that carried the discount. | PASS-10OFF |
| loyalty | true | Pass Redeemed when a loyalty reward was used | Marks a loop conversion. Absent on every other redemption. | true |
| order_id | string | Order Completed | The store's order number as a string (Segment e-commerce spec). | 1042 |
| total | number | Order Completed | The order total as the store reported it. | 64.9 |
| currency | string | Order Completed | ISO 4217 code as the store reported it. | EUR |
| products[] | array of { name, sku, quantity, price } | Order Completed | One entry per line item: title, SKU (absent when the store has none), quantity, unit price as a number. | [{ "name": "Hydra serum 30 ml", "sku": "HS-30", "quantity": 1, "price": 64.9 }] |
| coupon | string | Order Completed | Every discount code on the order, comma-separated. Absent when the order used none. | 1005 |
| programId | string | Loyalty Pass Issued, Loyalty Points Earned, Loyalty Reward Unlocked, Loyalty Tier Changed | The loyalty program. | p_0Zk4Lm |
| earned | number | Loyalty Points Earned, Loyalty Reward Unlocked | Points added by this event. | 12 |
| balance | number | Loyalty Points Earned, Loyalty Reward Unlocked | The balance after this event. | 47 |
| direction | "up" | "down" | Loyalty Tier Changed | Whether the member moved to a higher or a lower tier. Up happens at the award that crosses the bar; down at a period boundary. Forwarded since 2026-10-24, like the five rows below. | up |
| tierIndex | number | Loyalty Tier Changed | The position of the new tier in the program's tier list; 0 is the base tier. | 2 |
| tierTitle | string | Loyalty Tier Changed | The new tier's title as the merchant named it; the text the card's Tier row now shows. | Gold |
| previousTierIndex | number | Loyalty Tier Changed | The position of the tier the member left. Absent on the member's first tier change, when no tier was stored before it (a first evaluation that lands above the base tier). | 1 |
| block | string | Loyalty Tier Changed | The period the evaluation ran in: "lifetime" for lifetime tiers, the year ("2026") for tiers measured in years, the block's first month ("2026-07") for tiers measured in months. | 2026 |
| blockEarned | number | Loyalty Tier Changed | Points earned in that period (the lifetime total for lifetime tiers). Tiers are decided on points earned, never on the balance, so a redemption never demotes. | 1520 |
| fromPassId | string | Pass Journey Advanced | The step the card left. | dqSZHxwuVVGjAtEY6P7o |
| toPassId | string | Pass Journey Advanced | The step the card moved to. | k2Pq7RtVwXyZ01234567 |
| registerId | string | Member Visited; Member Updated from the staff app or from an API call that reported a register | The id of the register (staff-app scanner) that counted the visit or made the change. Absent on changes made in the Studio or by an API call without a register. | r_7Hq2Lk |
| registerName | string | Member Visited; Member Updated from the staff app | The register's name as set in Settings → Registers. | Front desk |
| visitCount | number | Member Visited | The holder's counted visits on this card, including this one. | 7 |
| changedFields | string[] | Member Updated | Every field key the change touched, in every mode: points, tier, memberName, memberId, or a custom field key of the pass. | ["points", "memberName"] |
| changes[] | array of { key, from, to } | Member Updated | One entry per changed field. It carries from and to as strings when the privacy sheet allows that field's values: a field in a contact class (name, email, phone, address, birthday, member ID, by the words in its key) follows its Core box, any other field follows its General box, and a value shaped like an email or an international phone follows the Core email or phone box whatever the field. Otherwise the entry carries the key only. Points, tier, member since and visit counts always carry their values. | [{ "key": "points", "from": "35", "to": "47" }, { "key": "memberName" }] |
| pointsFrom | number | Member Updated when points changed | The balance before the change. | 35 |
| pointsTo | number | Member Updated when points changed | The balance after the change. | 47 |
| pointsDelta | number | Member Updated when points changed | pointsTo minus pointsFrom; negative on a deduction. On a program-backed award it equals the earned of the matching Loyalty Points Earned. | 12 |
| tier | string | Member Updated when the change moved the tier | The new tier's title, on a membership card whose tiers are set on the pass. A program's tier move is its own Loyalty Tier Changed. | Gold |
| previousTier | string | Member Updated when the change moved the tier | The title of the tier the member left. | Silver |
| note | string | Member Updated, when Staff notes is on | The note staff typed with the change, up to 280 characters. Withheld while the Staff notes row of the privacy sheet is off (the Full identity preset turns it on). | Birthday bonus |
Two kinds of campaign fields
The utm* fields describe the event's own touch: the link that was opened for a view or an install, the channel for an order or a redemption. The acquisition* fields describe the first touch: the link that created the card, stamped once at creation and carried unchanged onto every later event of that card, through journey steps and loyalty loops. On Pass Installed the two coincide, so the install carries the campaign in the utm* fields and acquisitionPartner / acquisitionClickId only. A Pass Redeemed three weeks later carries utmSource = pass-studio_shopify (how the redemption reached us) and acquisitionSource = meta (what acquired the holder). Attribution reports group by the acquisition* fields.
Who the event is about
Every track call names the person it is about, in one of three ways, in this order of preference:
userId= the Pass Studio holder ID, when the event comes from one holder's own card: an install that identified the holder, a redemption or a scan of their own code, an order that reached their card, every loyalty event. The call also carries the card's serial asanonymousId, so Segment merges the card's earlier anonymous events onto the same profile.anonymousId= the card's serial, when the card exists but no holder has been identified yet. Once the holder is identified, later calls carry both ids and Segment stitches the history onto the profile.anonymousId=pass:<passId>, when no single card can be tied to the event: an order that issued nothing, an order paid with a code that every holder of a simple-mode pass shares, a journey step recorded before 2026-10-24. Pass Viewed uses the share token instead, which is the link and never a person. None of these ever lands on a person's profile.
With Holder ID off on the privacy sheet (the Events only preset), rule 1 never applies: no track call carries userId, every event rides on the card's serial, the share token or pass:<passId> as anonymousId, and no identify or alias is sent. Attribution still works per event, because the acquisition fields are on the event, not on a profile.
The holder ID is an opaque six-character identifier Pass Studio mints when a card is created (for example JZNJLI). It is the same across all of a holder's cards in your workspace, it is not derived from an email or a name, and it is the key the identify calls below use, so track events and profile traits meet on one profile.
The shared-code rule
A simple-mode pass has one barcode for every holder, so the code on an order cannot say which holder bought. Since 2026-10-24 Pass Studio finds the buyer's own card by the order's email: exactly one active card of that pass with that contact email is the buyer's, and Order Completed and Pass Redeemed then carry that holder as userId, the card's serial, and the card's own first touch in the acquisition* fields. When no card matches, or more than one does (a shared household address, two installs), both events are sent on anonymousId = pass:<passId> with no userId and no acquisition* fields at all. Another holder's card is never used. A buyer who added the pass from a link without giving an email cannot be matched and stays unattributed. From 2026-10-23 to 2026-10-24 these events carried no holder but did carry an arbitrary card's first touch; before 2026-10-23 they carried that card's holder as well. A unique-mode or pool-mode pass gives every holder their own code, and those orders always name the holder.
Every call's envelope
timestamp: the moment the event happened, ISO 8601, UTC.context.library:{ "name": "pass-studio", "version": "1" }.messageId: on Order Completed,order.processed:followed by Pass Studio's order reference. Segment drops a second call with the samemessageId, so a store webhook delivered twice yields one Order Completed. Other events carry nomessageId.
Customer profiles: the privacy sheet
Since 2026-10-24 Settings → Integrations → Segment carries one privacy sheet per workspace. It decides what may leave for Segment: the profile identifier, the identify traits, and which field values travel on Member Updated. Attribution fields (utm*, acquisitionSource, acquisitionMedium, acquisitionCampaign, acquisitionPartner), the pass, the platform, order amounts, points, tier, member since and visit counts are sent in every state and are not on the sheet. The sheet governs Segment only; webhooks, Zapier and Shopify Flow are unaffected.
- Core — kinds of data. Holder ID is the profile identifier: on, track calls carry the holder ID as
userIdwith the card's serial asanonymousId, andidentifyandaliasare sent; off, every track call carries the card's serial (or the share token, orpass:<passId>) asanonymousIdand nothing else, and noidentifyoraliasis sent. Click ID governsacquisitionClickIdon track calls; partner postbacks are unaffected. Six contact classes — name, email, phone, address, birthday, member ID — each govern theidentifytrait of that class and the before and after values on Member Updated of any field in that class, whatever the field is called (memberName,shippingAddress,birthDate,member_id). The contact classes need Holder ID. - General — your own field keys, the union across every pass of the workspace (draft, active and archived), each with a checkbox. Checked: the value travels on Member Updated. Unchecked, the default: the key travels, the value does not. A Staff notes row governs the note. A value shaped like an email address or an international phone number follows the Core email or phone box whatever its field.
- Presets — the three modes are named states of the sheet. Events only: nothing in Core checked. Anonymous personas, the default: Holder ID and Click ID only. Full identity: all of Core and the staff note. Any other state shows as Custom. Your General choices hold across presets. A connection made before the sheet keeps its mode as the matching preset; one that was on Full identity keeps sending every field's values, and shows every General box checked, until you save the sheet.
An identify is sent when a holder is created or identified, at install or at wallet registration. An identical identify is not sent twice for the same holder; it is sent again when a trait changes.
| Trait | Sent when | Meaning |
|---|---|---|
| holderConfidence | Holder ID on | "high" when the holder was identified by an email or member ID, "low" for an anonymous wallet add. |
| firstSeenAt | Holder ID on | ISO 8601 timestamp of the holder's first card. |
| passCount | Holder ID on | How many cards the holder has. |
| acquisitionSource, acquisitionMedium, acquisitionCampaign | Holder ID on | The first-touch campaign of the holder's first card, when known. |
| platform | Holder ID on | "apple" or "google", the wallet of the card that created the profile. |
| Holder ID and Email on | The holder's email, when identified from an order, a form, or a loyalty enrollment. | |
| phone | Holder ID and Phone on | The holder's phone number in international format, when identified from an order or a form (since 2026-10-24). |
| memberId | Holder ID and Member ID on | The holder's member ID on a membership card, when there is one. |
When Pass Studio's identity engine decides that two holder records are the same person (for example an anonymous install later matched to an order by email), it sends an alias call with previousId = the record that was merged away and userId = the surviving holder ID, so the complete history stitches onto one profile in destinations that support aliasing. With Holder ID off no alias is sent.
Complete example payloads
One holder, two events. On 2026-10-02 a customer taps a Meta ad, lands on the share page with the ad's parameters, and installs the card. The install identified the holder (an email on the form), so the call carries userId.
{
"event": "Pass Installed",
"userId": "JZNJLI",
"anonymousId": "8b00f894-cda3-5355-9e78-c5c925a32efa",
"timestamp": "2026-10-02T14:03:11.482Z",
"properties": {
"passId": "dqSZHxwuVVGjAtEY6P7o",
"platform": "apple",
"shareToken": "JSB11lYjQiAgGlD_j9GU9w",
"utmSource": "meta",
"utmMedium": "cpc",
"utmCampaign": "spring_sale",
"utmContent": "video_b",
"utmTerm": "k1"
},
"context": { "library": { "name": "pass-studio", "version": "1" } }
}Three weeks later the same person pays an order in the Shopify store with the card's own code, 1005. The order's channel is in the utm* fields, the acquiring campaign in the acquisition* fields, and the call names the same holder.
{
"event": "Order Completed",
"userId": "JZNJLI",
"anonymousId": "8b00f894-cda3-5355-9e78-c5c925a32efa",
"timestamp": "2026-10-23T09:41:07.019Z",
"messageId": "order.processed:<our order reference>",
"properties": {
"passId": "dqSZHxwuVVGjAtEY6P7o",
"platform": "web",
"utmSource": "pass-studio_shopify",
"utmMedium": "order",
"utmCampaign": "order_1042",
"acquisitionSource": "meta",
"acquisitionMedium": "cpc",
"acquisitionCampaign": "spring_sale",
"order_id": "1042",
"total": 64.9,
"currency": "EUR",
"products": [
{ "name": "Hydra serum 30 ml", "sku": "HS-30", "quantity": 1, "price": 64.9 }
],
"coupon": "1005"
},
"context": { "library": { "name": "pass-studio", "version": "1" } }
}An order paid with a shared code on a simple-mode pass, where the order email matched no card of the pass or more than one, names no one and carries no acquisition fields:
{
"event": "Order Completed",
"anonymousId": "pass:k2Pq7RtVwXyZ01234567",
"timestamp": "2026-10-23T10:02:44.310Z",
"properties": {
"passId": "k2Pq7RtVwXyZ01234567",
"platform": "web",
"utmSource": "pass-studio_shopify",
"utmMedium": "order",
"utmCampaign": "order_1043",
"order_id": "1043",
"total": 19,
"currency": "EUR",
"products": [{ "name": "Lip balm", "quantity": 1, "price": 19 }],
"coupon": "WELCOME10"
},
"context": { "library": { "name": "pass-studio", "version": "1" } }
}Staff at the front desk award the first holder 12 points and correct the spelling of their name, with a note, in a workspace on Anonymous personas mode. The points travel as numbers, the name correction as its key alone, and the note is not sent. In Full identity mode the second entry would read { "key": "memberName", "from": "Dana Lev", "to": "Dana Levi" } and a note property would follow.
{
"event": "Member Updated",
"userId": "JZNJLI",
"anonymousId": "8b00f894-cda3-5355-9e78-c5c925a32efa",
"timestamp": "2026-10-24T16:12:40.118Z",
"properties": {
"passId": "dqSZHxwuVVGjAtEY6P7o",
"platform": "web",
"acquisitionSource": "meta",
"acquisitionMedium": "cpc",
"acquisitionCampaign": "spring_sale",
"changedFields": ["points", "memberName"],
"changes": [
{ "key": "points", "from": "35", "to": "47" },
{ "key": "memberName" }
],
"pointsFrom": 35,
"pointsTo": 47,
"pointsDelta": 12,
"source": "register",
"registerId": "r_7Hq2Lk",
"registerName": "Front desk"
},
"context": { "library": { "name": "pass-studio", "version": "1" } }
}Where else the data lives
Every event is first written to Pass Studio's own analytics store (BigQuery), with more than Segment receives: the full landing query string of a view or an install, the referrer, the country, region and city derived from the request at the time, the device identifiers, and the per-event metadata listed in the Event Reference. The Analytics and Reports pages in the Studio read from it, and each report widget exports CSV. Signed webhooks and Zapier triggers come from the same stream; their payloads add the holder's contact where known and are documented on their own pages. Nothing on this page changes what BigQuery, webhooks or Zapier receive.
What Pass Studio never sends to Segment
- Email, phone number and name on any track call while their Core boxes on the privacy sheet are off (the Events only and Anonymous personas presets). With a box on, that class travels as an identify trait (email, phone, memberId) and as the before and after values of a field in that class on Member Updated; no other track call carries them in any state.
- Any URL parameter you have not allowed. Pass Studio reads utm_source, utm_medium, utm_campaign, utm_content, utm_term, pid and cid on its own; every other parameter (gclid, fbclid, msclkid, ttclid, utm_id, a promo code key, anything custom) is forwarded only when you list it under Settings → Integrations → Segment → Link parameters, under its own name, on Pass Viewed, Pass Installed and Pass Reinstalled. The full landing query string stays in Pass Studio's own analytics (BigQuery) and is never sent as a whole.
- The referrer of the share page (stored in Pass Studio's analytics only).
- Device identifiers: the Apple device library identifier, push tokens, the Google object id.
- Wallet plumbing: register, unregister and fetch events, the daily health check.
- On Member Updated: the before and after values of a field whose Core class box or General box is off, any value shaped like an email or a phone while the Core email or phone box is off, and the staff note while Staff notes is off. The field key is always sent.
- On Loyalty Tier Changed: the tier period's unit and length, and the tier's since timestamp. They are in the holder.tier_changed webhook only.
- The holder on Pass Viewed (keyed on the share link, never a person).
- Historical events from before the connection was made. Nothing is replayed.
Questions and answers
Does Pass Studio send utm_content and utm_term to Segment?
Yes, since 2026-10-23, as utmContent and utmTerm on Pass Viewed, Pass Installed and Pass Reinstalled, when the landing URL carried them. They are not stamped as first touch, so a later Pass Redeemed or Order Completed carries acquisitionSource, acquisitionMedium and acquisitionCampaign only.
Does Pass Studio send gclid, fbclid or other click ids?
Only when you allow them. Under Settings → Integrations → Segment → Link parameters, list the parameter names you want (gclid, fbclid, msclkid, ttclid, a promo code key, anything on your links). Each listed key is forwarded under its own name with its value on Pass Viewed, Pass Installed and Pass Reinstalled when the landing URL carried it. Nothing you did not list leaves the full landing query string, which Pass Studio keeps in its own analytics. utm_source, utm_medium, utm_campaign, utm_content, utm_term, pid and cid are read without being listed. On a view or install the value comes from that link; on every later event of the card it comes from the link that acquired the card, kept from the moment the card was created, so an allowed gclid rides the card's Pass Redeemed and Order Completed too. Cards created before 2026-10-24 carry no acquiring link until Pass Studio's backfill has run. Since 2026-10-24.
How do I attribute an order's revenue to the campaign that acquired the customer?
Group Order Completed by acquisitionSource, acquisitionMedium and acquisitionCampaign. Those three are the first touch of the card the order was paid with, stamped when the card was created. The utm fields on the same event describe the order's own channel (for example pass-studio_shopify / order / order_1042) and are not the acquiring campaign.
Why does an order show utmSource = pass-studio_shopify?
The utm fields always describe the event's own channel. An order reached Pass Studio through the Shopify connection, so its utmSource is pass-studio_shopify, its utmMedium is order and its utmCampaign is order_<number>. The campaign that acquired the buyer is in acquisitionSource, acquisitionMedium and acquisitionCampaign.
Why do some Order Completed events have no userId?
Two cases. The order issued nothing (no pass matched, no credits, an invite was sent instead), so there is no holder yet. Or the order was paid with the code of a simple-mode pass, which every holder of that pass shares, and the order's email matched no active card of that pass, or more than one, so no single person can be named. Both land on anonymousId pass:<passId> with no acquisition fields. A shared-code order whose email matches exactly one card does carry that holder.
Which events carry the holder as userId?
Pass Installed, Pass Reinstalled and Pass Removed when the install identified a holder; Pass Redeemed when an online order used one holder's own code, when a shared code was resolved by the order email, when a loyalty reward was used, or when the card scanned at a counter, a Square register or over the API carries its own code (since 2026-10-24); Order Completed when the order reached one holder's own card; Pass Journey Advanced when the card that moved is one holder's own (since 2026-10-24); Loyalty Pass Issued, Loyalty Points Earned, Loyalty Reward Unlocked, Loyalty Tier Changed, Member Visited and Member Updated always. Pass Viewed is keyed on the share link.
What does Member Updated carry with the Anonymous personas preset?
Holder ID and Click ID on, every contact class and every General box off: the field keys that changed (changedFields), the points before and after and the delta as numbers, the tier titles, who made the change (source, registerId, registerName) and the card's first-touch acquisition. Every entry in changes carries the key only, except points, tier, member since and visit counts. The staff note is not sent. Check a General box for a field whose values your CRM needs, a Core box for a contact class, or pick Full identity.
Why does one points award produce two events?
On a card enrolled in a loyalty program, an award at the staff app, in the Studio or over the API runs through the program, which sends Loyalty Points Earned (earned, balance, programId). The same award is also a ledger entry on the member's card and sends Member Updated (pointsDelta, pointsFrom, pointsTo, the register, the note). Count points from one of the two, not both. A card with points on the pass itself and no program sends Member Updated only.
Does Loyalty Tier Changed carry the tier name?
Yes, since 2026-10-24: tierTitle is the new tier's title, tierIndex its position (0 = the base tier), direction is "up" or "down", previousTierIndex the tier the member left (absent on the first change), block the period evaluated ("lifetime", "2026" or "2026-07") and blockEarned the points earned in it. The period's unit and length are not on the event; the holder.tier_changed webhook carries them.
Why do events from before 2026-10-24 look different?
Before 2026-10-23 Order Completed carried no userId and no acquisition fields; every order of a pass sat on anonymousId pass:<passId>. Loyalty Points Earned, Loyalty Reward Unlocked and Loyalty Tier Changed were keyed on the loyalty program's member record id instead of the holder id, so they did not join the holder's profile. utmContent and utmTerm were not forwarded. Before 2026-10-24 Loyalty Tier Changed carried passId, platform and programId only, Pass Journey Advanced sat on anonymousId pass:<passId> with no acquisition fields, and Member Visited and Member Updated were not sent at all. Older events were not rewritten.
What is the holder ID, and is it personal data?
An opaque identifier Pass Studio mints when a card is created, six characters such as JZNJLI. It is not derived from an email or a name, it is the same across all of the holder's cards in your workspace, and it never leaves Pass Studio with an email or phone attached unless you turn on the Email or Phone box of the privacy sheet.
Does the Segment connection get the holder's email?
Only while the Email box of the privacy sheet is on (the Full identity preset turns it on): as a trait on identify calls, and on Member Updated as the before and after values when staff corrected an email field or any value shaped like an email. With the box off no track call and no identify call carries an email; the same holds for the phone number with the Phone box and for names with the Name box.
Which Segment workspace receives an event when I use teams?
The workspace connected in the Pass Studio workspace that owns the pass. A team pass streams to the team's Segment connection; a personal pass streams to the personal connection. Routing follows the pass, not the person who connected.
How fast do events arrive, and what if Segment is down?
Each event is forwarded within seconds of being recorded, with one HTTP call to Segment's API and a 3-second timeout. A failure is recorded as the connection's last error and the event is not retried. Nothing about passes, redemptions or Pass Studio's own analytics depends on Segment. A change to the connection's settings takes effect within 60 seconds.
Are duplicate events possible?
Store webhooks are sometimes delivered twice. Order Completed carries a messageId built from Pass Studio's own order reference, and Segment drops a second call with the same messageId, so a retried order webhook produces one Order Completed.
Can I replay past events after connecting?
No. Forwarding starts at the moment the connection is saved. Pass Studio's own analytics hold the full history.
Is the Segment connection available on my plan?
Yes. It is available in every Pass Studio workspace, personal or team, on every plan, and Segment's HTTP API source is on every Segment plan including the free one.
Changelog
- 2026-07-21 — Segment source launched: track calls for every pass event, Order Completed in the e-commerce spec, three profile modes, identify at install, alias on holder merge.
- 2026-10-23 — Order Completed carries the holder as userId, the card's serial as anonymousId, and the first-touch acquisition fields when the order reached one holder's own card. Loyalty Points Earned, Loyalty Reward Unlocked and Loyalty Tier Changed are keyed on the holder id instead of the program's member record id. Pass Redeemed from an online order carries the holder. utmContent and utmTerm are forwarded on views and installs. A code shared by every holder of a simple-mode pass never names a person.
- 2026-10-24 — Loyalty Tier Changed carries direction, tierIndex, tierTitle, previousTierIndex, block and blockEarned. Pass Journey Advanced carries the holder as userId, the card's serial as anonymousId, and the first-touch acquisition fields when the card that moved is one holder's own; its source values are documented as sent: scan, order, time, heal, api. Two new events: Member Visited (a counted staff-app scan of a membership card: registerId, registerName, visitCount) and Member Updated (the staff ledger entry: changedFields, changes, pointsFrom, pointsTo, pointsDelta, tier, previousTier, source, registerId, registerName; the before and after values of person-level fields and the note in Full identity mode only); both carry the holder as userId, the serial as anonymousId and the card's first-touch acquisition. Fourteen event names. Older events were not rewritten. On a code shared by every holder of a simple-mode pass, the buyer's own card is found by the order email: exactly one match names the holder and carries their first touch; none or several leaves the order and the redemption with no holder and no acquisition fields, never another holder's.
- 2026-10-24 — Link parameters: a per-workspace allowlist under Settings → Integrations → Segment. Each listed landing-URL parameter (gclid, fbclid, msclkid, a promo code key, anything) is forwarded under its own name with its value on Pass Viewed, Pass Installed and Pass Reinstalled. A parameter not listed is never sent; the full query string stays in Pass Studio's analytics. On every later event of a card the value is the acquiring link's, kept from the card's creation.
- 2026-10-24 — The privacy sheet replaces the three radio buttons under Settings → Integrations → Segment. Core: Holder ID (the profile identifier — off means no userId, no identify, no alias), Click ID, and six contact classes (name, email, phone, address, birthday, member ID) that govern identify traits and Member Updated values of any field in the class. General: every field key your passes define, value on or off, plus Staff notes. The three modes are presets of the sheet; anything else is Custom. New identify trait phone with the Phone box. A connection from before the sheet keeps its mode as the matching preset.
- 2026-10-24 — Pass Redeemed from a staff-app counter scan, a Square register sale or an API redemption (POST /v1/redeem, POST /v1/loyalty/redeem) carries the holder as userId and the card's serial as anonymousId when the scanned code is one holder's own (unique or pool mode, a membership card, a loyalty card). Before, those channels carried the first touch but no holder and sat on anonymousId pass:<passId>.
Questions?
Email support@thepassstudio.com or use the assistant in the bottom-right corner of the dashboard.