WalletWallet API
Back to Blog

How to Send Apple Wallet Pass Notifications with a Custom Message

Apple Wallet has no send-message API: a lock-screen banner appears when a pass field changes. Send any custom message with changeMessage and one PUT, and see how APNs and Google Wallet messages work underneath.

2026-10-18 By Alen Todorov

An Apple Wallet pass shows a lock-screen notification when one of its fields changes value and that field carries a changeMessage. There is no separate endpoint for sending a message, so to send any text you want, you change a field whose value is the text.

With WalletWallet that field is the notification property: PUT a new message whenever you want a banner. Both wallets are handled, and you never touch APNs or Google’s API.

An iPhone lock screen showing a Bayroast Coffee Wallet notification: Double stamps all day Saturday.
Photo by Jamie Street on Unsplash, edited.

Send a custom message

Create the pass as usual. It needs no message field yet.

curl -X POST https://api.walletwallet.dev/api/passes \
  -H "Authorization: Bearer ww_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationName": "Bayroast Coffee",
    "logoText": "Bayroast Coffee",
    "primaryFields": [
      { "label": "Stamps", "value": "3 / 8", "changeMessage": "You have %@ stamps" }
    ],
    "barcodeValue": "BR-0412",
    "barcodeFormat": "QR"
  }'

The response returns a serialNumber. To send a message, PUT the same pass with notification set to the message:

curl -X PUT https://api.walletwallet.dev/api/passes/<serial> \
  -H "Authorization: Bearer ww_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationName": "Bayroast Coffee",
    "logoText": "Bayroast Coffee",
    "primaryFields": [
      { "label": "Stamps", "value": "3 / 8", "changeMessage": "You have %@ stamps" }
    ],
    "barcodeValue": "BR-0412",
    "barcodeFormat": "QR",
    "notification": "Double stamps all day Saturday."
  }'

Every iPhone with the pass installed shows “Double stamps all day Saturday.” on the lock screen, under the name in organizationName. On Apple the message is a back field labeled Notifications, with changeMessage "%@", so the banner shows the text exactly as written. PUT replaces the whole pass, so send every field each time. The exception is notification: an update that omits it keeps the current message, and "notification": null removes it.

How Apple decides to show a notification

Apple’s field reference defines changeMessage as “a format string for the alert text to display when the pass updates”, and sets three rules:

  1. The field needs a changeMessage. “You need to provide a value for the system to show a change notification.” A changed field without one updates silently.
  2. The format string needs %@, which iOS replaces with the field’s new value. "Gate changed to %@" becomes “Gate changed to B12”.
  3. The value has to change. iOS compares the new pass with the one on the device and notifies for fields whose value differs.

That gives you two patterns:

  • Templated banners on real fields. Put the changeMessage on the field that changes: "You have %@ stamps" on a stamp count, "Now at %@" on a gate, "Your balance is %@" on a balance. Updating the count is the notification.
  • Free-form messages. The notification property, as in the example above. Any text you send becomes the banner.

Change one notifying field per update. When two fields with a changeMessage change together, iOS shows one generic “Pass changed” banner with neither text.

Rules for the message field

  • Send it alone. Put a message in an update that changes no other field with a changeMessage, so the banner shows its text.
  • Keep backFields in the same order for your own fields with a changeMessage. WalletWallet identifies them by their position in the array, so moving one re-keys it. The notification field has its own key and never moves.
  • Vary the text. Sending the same message twice is not a change, so the second one shows nothing. An identical request body returns { "unchanged": true }, fires no push, and doesn’t count against your quota.
  • Keep it short. The lock screen shows a few lines; put detail in the pass itself, where the message also stays readable on the back of the card.

The Pass Manager sends the same message without code: open the pass, type the message under “Send a notification”, and send.

What happens underneath on Apple

If you run passes on your own infrastructure, this is the chain that a WalletWallet PUT performs for you. Apple documents it under updating a pass.

  1. The pass declares a web service. pass.json carries webServiceURL and an authenticationToken. Without both, the pass can never update.
  2. The device registers. When someone adds the pass, the device sends POST /v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber} with its push token, authenticated with Authorization: ApplePass <token>.
  3. You push. When the pass changes, send an APNs notification to each registered push token. The payload is an empty JSON object, {}.
  4. The device asks what changed. It calls GET /v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}?passesUpdatedSince={tag}, and you return the serial numbers updated since that tag plus a new tag.
  5. The device downloads the pass. It calls GET /v1/passes/{passTypeIdentifier}/{serialNumber} and receives the new signed .pkpass. iOS compares field values and shows a banner for each changed field with a changeMessage.

The APNs request has details that trip up most first implementations:

  • Authentication: token-based, with a .p8 key from your Apple Developer account signing an ES256 JWT whose kid is the key ID and iss is your team ID.
  • Topic: apns-topic is the pass type identifier (pass.com.example.loyalty), not an app bundle ID.
  • Host: pass pushes go to the production host, api.push.apple.com.
  • Content: the new .pkpass must actually differ in a field value, or the device downloads it and shows nothing.

When passes don’t update, the cause is usually one of those, or the step 4 endpoint returning no serials because the passesUpdatedSince tag isn’t compared correctly.

Google Wallet

Google Wallet notifies through messages. Adding a message to a pass with the TEXT_AND_NOTIFY type pushes a notification to everyone who saved it, and the message appears in the pass details. Google allows 3 notifying messages per pass in 24 hours and returns a quota error past that.

On every PUT, WalletWallet updates the Google pass and adds a message with TEXT_AND_NOTIFY. The Android notification reads “title was updated”, and the new content, including your message field, shows inside the pass. Past Google’s daily limit, we resend the same message as a silent TEXT message, so the pass still updates without a notification.

Sending well

  • Tie each message to the pass. A stamp, a reward, a changed time, or an offer for the holder of that card reads as service. A general promotion reads as spam, and holders can switch notifications off per pass on the back of the card.
  • Plan for Google’s cap. Three notifications per pass per day is the ceiling on Android, so batch related changes into one update.
  • Say who it’s from. organizationName is the banner title, so set it to the business the holder knows.

FAQ

Can I send an Apple Wallet notification without changing the pass?

No. Wallet has no message endpoint; a notification is always the result of a changed field. The notification property above is the standard way to send any text.

Why didn’t my notification show?

Check, in order: the field has a changeMessage containing %@; its value differs from the last update; no other field with a changeMessage changed in the same update; backFields kept its order; and the holder hasn’t turned off notifications for the pass.

How fast does it arrive?

Devices get the push within seconds of the PUT, then download the new pass. A phone that is off or offline updates when it reconnects.

Does every message count against my plan?

Each PUT that changes the pass counts as one pass on your plan, on both wallets together. An identical body is free and sends nothing.

Can I customize the notification on Android?

Google’s notification text is generic. Your message shows inside the pass, in the details view.


To send your first message, create an account, issue a pass, and PUT it with a notification. The full request reference is in the docs. For the fields on the pass itself, see the anatomy of an Apple Wallet pass.

Build, distribute, and notify in under five minutes

Design your passes, hand them out, and send push notifications to Apple Wallet and Google Wallet. By API, from the dashboard, or through an AI assistant.

Start free Read Docs Works with ChatGPT Claude Zapier