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 is two requests: create the pass with a message field once, then 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 in two requests

Create the pass with a back field reserved for messages. Its changeMessage is "%@", so the banner shows the field’s new value exactly as written.

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" }
    ],
    "backFields": [
      { "label": "Messages", "value": " ", "changeMessage": "%@" }
    ],
    "barcodeValue": "BR-0412",
    "barcodeFormat": "QR"
  }'

The response returns a serialNumber. To send a message, PUT the same pass with the message as the field’s new value:

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" }
    ],
    "backFields": [
      { "label": "Messages", "value": "Double stamps all day Saturday.", "changeMessage": "%@" }
    ],
    "barcodeValue": "BR-0412",
    "barcodeFormat": "QR"
  }'

Every iPhone with the pass installed shows “Double stamps all day Saturday.” on the lock screen, under the name in organizationName. PUT replaces the whole pass, so send every field each time, not only the one that changed.

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 on an anchor field. A back field whose only job is to carry messages, with changeMessage: "%@", as in the example above. Any text you put in value becomes the banner.

Change one notifying field per update when you want one clear banner.

Rules for the message field

  • Seed it when you create the pass. The field has to exist on the pass before an update can change it. If the first PUT introduces the field, iOS sees a new field rather than a changed value, and that first message is silent.
  • Keep backFields in the same order. WalletWallet identifies fields by their position in the array, so moving the message field re-keys it and the banner stops firing.
  • 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 same anchor works from the Pass Manager without code: open the pass, type the new message into the field, and save.

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 message field above is the standard way to send any text.

Why didn’t my notification show?

Check, in order: the field has a changeMessage containing %@; the field existed on the pass before this update; its value differs from the last 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 with a message field, and PUT a new value. 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