Enhancing User Experience: Push Notifications in .NET for Apple Wallet Pass Updates

By admin

Apple
ORG

Wallet Passes Passes are dynamic and they reflect real-world state. Information in

Apple
ORG


Wallet
PRODUCT

passes can be dynamically updated. Learn how to use

Apple Push Notification Service
ORG

and Web Service endpoint to keep

Wallet Pass
ORG

updated.


Apple Wallet Passes Passes
ORG

are dynamic and they reflect real-world state. Information in

Apple
ORG


Wallet
PRODUCT

passes can be dynamically updated.

Updating a Pass is easily done by distributing a new version of the

Pass
LOC

with the same identifier and serial number.

Alternatively, you can add an webServiceURL and authentication Token keys in the initial

Wallet Pass
FAC

added to the device. The device then uses the service endpoint to communicate with the server about the changes to passes and to fetch the latest version of the Pass as it changes.

In this blog post, let’s learn how to set up Push Notification updates to

Apple Wallet Passes
ORG

from a .NET application.

The high-level flow remains the same regardless of whichever programming language you use to build the application.

I will use

the Lambda Annotations Framework
LAW

to build these API endpoints. However, you can use your existing application hosting mechanism for this.

This article is sponsored by

AWS
ORG

and is part of my AWS Series.

Updating a Wallet Pass

The Device the Pass is added and

the Pass Provider’s
ORG

web server works together to keep

the Wallet Pass
LOC

updated.

Below are the key communication steps between the device and the server to keep

the Wallet Pass
LOC

updated.

The device registers for

Pass Updates
ORG

to the webServiceURL provided in the pass, as soon as the

Pass
ORG

is added. On changes to the

Pass
LOC

on the Web server, it uses the device information obtained in the registration request to send a push notification for the Pass Type On receiving

the Push Notification
WORK_OF_ART

, the user device reaches out to the webServiceURL to get a list of passes that has changed For each

Pass
LOC

that has been updated, it asks the server for the latest version of the

Pass
LOC

.

Request/Response flow involved in updating a

Apple Wallet Pass
ORG

using a Web Service Url (source – apple developer documentation page)

The above diagram summarizes the high-level request/response flow between the user device and the web server delivering the Apple Wallet Pass updates.


Implementing the Web Service
ORG

Endpoint


The Apple PassKit Web Service Reference
ORG

provides the API Endpoint specifications required to support

Wallet Pass
ORG

updates.

When creating a Pass file, we can set the WebServiceUrl property to set the callback service endpoint.

var request = new PassGeneratorRequest { … WebServiceUrl = "https://my-apple-wallet-pass-web-service-url.com/",

AuthenticationToken
PRODUCT

= "authentication-token-used-to-authenticate-calls-to-service" … }

You can set an

AuthenticationToken
PRODUCT

on the pass request, that will be passed back to

the Web Service Url
ORG

when the device calls it to get the pass updates. You can use this to authenticate requests coming to your endpoint.

Check out the post below on details on how to create an Apple Wallet Pass from a .NET application.

Below are the

5
CARDINAL

different endpoint capabilities that

the Apple PassKit Web Service Reference
ORG

expects from the server implementing the web service.

Register Device

The Register Device endpoint is POST method with the URL format as webServiceURL /version /devices/deviceLibraryIdentifier /registrations/passTypeIdentifier /serialNumber

Below is an

API
ORG

Endpoint using the Lambda Annotations framework

[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole")] [HttpApi(LambdaHttpMethod.Post, "/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber}")] public async Task RegisterDeviceForPushNotificationForAPass( string deviceLibraryIdentifier, string passTypeIdentifier, string serialNumber, [FromBody] RegisterRequest pushToken) { // Save Device Registration Info to data store }


Get Passes For Device

The Get Serial Numbers for Passes
WORK_OF_ART

associated with a device is a GET request to webServiceURL /version /devices/deviceLibraryIdentifier /registrations/passTypeIdentifier ?passesUpdatedSince=tag

The

GetPassesForDevice
FAC

method uses the deviceLibraryIdentifier and the passesUpdatesSince (from query parameters) to fetch the Passes that has been updated since the specified date.

It returns an array of

SerialNumber
ORG

strings that the

Apple
ORG

device then uses to enumerate and call the GetLatestPass method on.

[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole")] [HttpApi(LambdaHttpMethod.Get, "/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}")] public async Task<GetPassesForDeviceResponse> GetPassesForDevice( string deviceLibraryIdentifier, string passTypeIdentifier, [

FromQuery
PERSON

] string passesUpdatedSince, ILambdaContext context) { var updatedSerialNumbers = await

GetPassesUpdatedForDeviceSince
PERSON

( deviceLibraryIdentifier, passesUpdatedSince); return new GetPassesForDeviceResponse() { SerialNumbers = updatedSerialNumbers,

LastUpdated
ORG

= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(), }; } public class GetPassesForDeviceResponse { public string

LastUpdated
ORG

{ get; set; } public string[] SerialNumbers { get; set; } }

We can use any value for the

LastUpdated
ORG

property when returning the result. In the example here I am using the UnixTimeMilliseconds as the value returned.

The very

first
ORDINAL

time the

Apple
ORG

Device calls this endpoint it will pass in a numm value for the query parameter. Once we return back a valid response with the

LastUpdated
ORG

property specified, the

Apple
ORG

Device sends that in the subsequent request.

Get Latest Pass

The Get Latest Pass is a GET request to webServiceURL /version /passes/passTypeIdentifier /serialNumber

The

Getpass
ORG

endpoint method returns the latest pass information if it has been updated. The endpoint uses the header ‘If-Modified-Since’ value to determine if the server content is modified or not.

[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole")] [HttpApi(LambdaHttpMethod.Get, "/v1/passes/{passTypeIdentifier}/{serialNumber}")] public async Task<APIGatewayHttpApiV2ProxyResponse>

GetPass
ORG

( string passTypeIdentifier, string serialNumber, [FromHeader(Name = "If-Modified-Since")] string ifModifiedSince, ILambdaContext context) { var pass = await GetPass(serialNumber, passTypeIdentifier); if (DateTimeOffset.TryParse(ifModifiedSince, out

var lastUpdated) && lastUpdated <
ORG

pass.

LastUpdated
ORG

) return new APIGatewayHttpApiV2ProxyResponse() { Body = Convert.

ToBase64String(pass
ORG

.

PassContent
ORG

), IsBase64Encoded = true,

StatusCode
ORG

= (int)HttpStatusCode.OK, Headers = new Dictionary<string, string> { { "

Content-Type
WORK_OF_ART

", "application/vnd.apple.pkpass" }, { "

Last-Modified
WORK_OF_ART

", pass.

LastUpdated
ORG

.ToString("o") }, { "

Content-Disposition
WORK_OF_ART

", "attachment; filename=ticket.pkpass; filename*=UTF-8”ticket.pkpass" } } }; else return new APIGatewayHttpApiV2ProxyResponse() {

StatusCode
ORG

= (int)HttpStatusCode.

NotModified
ORG

}; }

If the pass content is updated it returns the new pass information along with the Last-Modified header value, which the

Apple
ORG

Device will send in as the ‘If-Modified-Since’ header value in the subsequent request.

For unmodified passes we just return a

HttpResponse
ORG

of

NotModified
ORG

.


Unregister Device
PERSON

The Unregister Device for Pass updates is a

DELETE
ORG

request to webServiceURL /version /devices/deviceLibraryIdentifier /registrations/passTypeIdentifier /serialNumber

The Unregister Device endpoint is very similar to the

Register
ORG

endpoint, only that it uses the Delete Http verb.

[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole")] [

HttpApi(LambdaHttpMethod
LOC

.Delete, "/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber}")] public async Task UnRegisterDeviceForPushNotificationForAPass( string deviceLibraryIdentifier, string passTypeIdentifier, string serialNumber, [FromBody] RegisterRequest pushToken) { // Remove Device from the registered list of devices for the Serial Number }

This is usually called when the pass is manually removed from the device and it no longer requires an update to be sent for that particular serial number.

In cases where the

Pass
LOC

is added to multiple devices, even if the

Pass
ORG

is removed from

one
CARDINAL

device, there can still be other devices that are still interested in the pass updates.


Logging
PERSON

Errors

The

Log
ORG

endpoint to catch any errors in the other endpoints is a POST request to webServiceURL/version/log

The

Log
ORG

endpoint is intended to help you debug your web service implementation. Any errors happening in the other Endpoints are sent to this generic endpoint so that we can debug and fix the issues in the endpoint.

[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole")] [HttpApi(LambdaHttpMethod.Post, "/v1/log")] public async Task Log([FromBody]

LogRequest logRequest
PERSON

) { // Log to standard logging }

You can log this to your standard logging console or endpoint and track it from there for errors in the API Endpoint.

Any time the information associated with

Wallet Pass
ORG

changes on our server side, we need to notify the

Devices
ORG

that have the Passes added to their

Wallet
PRODUCT

.

To notify devices we will use

the Apple Push Notification Service
ORG

(

APNS
ORG

).


dotAPNS
PERSON

is a

NuGet
PRODUCT

library used to send pushes to

Apple
ORG

devices via the HTTP/2 APNs API, which is an officially recommended way of interacting with APNs (

Apple
ORG

Push Notification service).

The same Pass can be added to multiple devices – if the user decides to share the passes or has multiple devices of their own.

For any

Pass
ORG

update, we need to push notify all the devices that have that pass added, to update it in all of them.

Using the IApnsService instance from the dotAPNS

NuGet
PRODUCT

library and the

PushToken
ORG

received as part of the Register Device API endpoint, we can notify all the devices that have the updated pass added to the wallet.

[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole")] [HttpApi(LambdaHttpMethod.Post, "/apple-wallet/push-updates/{serialNumber}")] public async Task PushUpdatesForPass(string serialNumber) { var deviceRegistrations = GetDeviceRegistrationForSerialNumber(serialNumber);

var applePushes
PERSON

= deviceRegistrations .Select(device => new ApplePush(ApplePushType.Background) .AddToken(device.

PushToken
ORG

) .AddContentAvailable());

var apnsResponses
PERSON

= await applePushes .Select(ap => _apnsService.

SendPushes(ap
GPE

.

ToArray
PERSON

(), _appleWalletConfiguration.PassbookCertificate())) .WhenAll(); }

We use the Certificate-based authentication with

APNS
ORG

in this case.

Note that no serial number or other information is passed along in the Push Notification to the devices.

Since the certificate has the information on

the Wallet Pass Type
FAC

, it passes that on to the devices automatically when delivering

the Push Notification
WORK_OF_ART

.


The Push Notification
WORK_OF_ART

triggers the device to call back to the WebServiceUrl configured for the

Pass
LOC

to get a list of updated Passes and the individual

Pass
LOC

.

Using push notifications we can notify the devices any time a Pass changes in the background (on our server) and have the device call back to our server endpoint to get the latest Pass details.

Most of the data exchange happens directly between the user device and our Web Server.