Add to Apple Wallet from Your .NET Application: A Step-by-Step Guide
Tags: dotnet, dotnetcore, aws, apple
Published: 2024-02-08 04:48:49

I recently worked on the 'Add to Apple Wallet' functionality at work, allowing the addition of Event tickets to Apple Wallet.

The iOS Wallet app allows users to manage payment cards, boarding passes, tickets, gift cards, and other passes.

The Wallet Pass is time and location enabled, so passes can be configured to display on the user’s device at the appropriate moment. Passes can also be updated with push notifications making it easy to notify users if details change.

{%embed https://youtu.be/mcR08yyFpaU %}

In this blog post, let's learn how you can set up, build, and distribute Apple Wallet passes using .NET application.

I will use an AWS Lambda Function to host the API endpoint for distributing the pass files. However, you can use your existing application hosting mechanism for this.

## Apple Wallet Passes

[Apple Wallet Passes](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html#//apple_ref/doc/uid/TP40012195-CH4-SW1) are created as a package/bundle, containing a `pass.json` file that defines the pass, and image assets such as the logo and the icon.

The `pass.json` file contains the information that identifies the pass, the text information, and details shown on the pass in the Wallet app.

The below image shows the files inside a sample pass file. It has the `pass.json`, logo and icon images, the manifest file, and also the signature file to avoid tampering with the pass file once it's generated.

![Apple Wallet Pass folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5109gxs5htfm06m37sdi.png)

Imaging showing the files inside a pass file. It contains the pass.json file, along with the icon and logo images, the manifest file and the signature file for the pass contents.

## Generating Wallet Passes Using .NET

To generate Apple Wallet Pass files from .NET applications, you can use the [dotnet-passbook](https://github.com/tomasmcguinness/dotnet-passbook) NuGet package.

The NuGet package makes it very easy and straightforward to create Wallet Pass files. The NuGet package provides a `PassGenerator` class that takes in one or more instances of `PassGeneratorRequest`.

The `PassGenerator` converts these requests into a byte array, which represents the pass bundle that contains one or more pass files.

```csharp
public async Task<APIGatewayHttpApiV2ProxyResponse> GetAppleWalletPasses()
{
    var eventId = Guid.NewGuid();
    var eventName = "YouTube Demo Event";
    var venueName = "YouTube Online";
    var eventDate = DateTime.Now.AddDays(55);

    var icon = await _appleWalletConfiguration.GetIcon();
    var logo = await _appleWalletConfiguration.GetLogo();

    var request = GeneratePassRequest(Guid.NewGuid().ToString(), eventId, icon, logo, eventName, eventDate, venueName, "Subscriber");

    var generator = new PassGenerator();
    var requests = new List<PassGeneratorRequest>() { request };
    var pass = generator.Generate(requests);
}
```

The `PassGeneratorRequest` identifies one pass that will be added to the Apple Wallet. If you have multiple passes to be added in the same bundle, create multiple `PassGeneratorRequest`.

The below code shows a sample `PassGeneratorRequest` that adds a pass with a `EventTicket` style. It sets the relevant properties to be shown on the Apple Wallet Pass.

```csharp
var request = new PassGeneratorRequest
{
    Style = PassStyle.EventTicket,
    PassTypeIdentifier = _appleWalletConfiguration.PassTypeIdentifier,
    SerialNumber = serialNumber,
    GroupingIdentifier = eventId.ToString(),
    BackgroundColor = "#823EB7",
    LabelColor = "#000000",
    ForegroundColor = "#ffffff",
    Images =
    {
        {PassbookImage.Icon, icon},
        {PassbookImage.Icon2X, icon},
        {PassbookImage.Icon3X, icon},
        {PassbookImage.Logo, logo},
        {PassbookImage.Logo2X, logo},
        {PassbookImage.Logo3X, logo},
    },
    Description = eventName,
    OrganizationName = "Rahul",
    RelevantDate = eventDate,
    ExpirationDate = eventDate.AddDays(1),
    AppleWWDRCACertificate = _appleWalletConfiguration.AppleWWDRCACertificate(),
    PassbookCertificate = _appleWalletConfiguration.PassbookCertificate()
};
request.AddHeaderField(new StandardField("time", eventDate.ToShortTimeString(),
    eventDate.ToShortDateString()));
request.AddPrimaryField(new StandardField("name", null, eventName));
request.AddSecondaryField(new StandardField("venue", "Venue", venueName));
request.AddAuxiliaryField(new StandardField("ticketType", "Ticket Type", ticketType));
request.AddBackField(new StandardField("ticketHolderName-back", "Ticket holder", "Rahul Nath"));
request.AddBackField(new StandardField("ticketType-back", "Ticket Type", ticketType));
```

For information on the different fields, the pass styles available, and what each property represents check out the [Wallet Guidelines](https://developer.apple.com/design/human-interface-guidelines/wallet) and the [documentation](https://developer.apple.com/documentation/walletpasses/pass).

### Setting Up Apple Certificates

To generate Apple Wallet Passes, you need to register with the Apple Developer Portal and have the appropriate certificates ready to sign the Wallet passes.

You need two certificates

* Your application/company Passbook certificate along with its password
    
* Apple WWDR (WorldWide Developer Relations) certificate
    

You can find the full instructions to generate the certificates in the dotnet-passbook NuGet package [documentation here](https://github.com/tomasmcguinness/dotnet-passbook#certificates).

You can store these certificates and other related configurations for generating the Wallet as part of the application configuration - *appsettings.json* file*.*

I chose to store the certificate files as base64 encoded string which can be converted to a `X509Certificate2` in .NET code as shown below.

```csharp
public class AppleWalletConfiguration
{
    public string WWDRCertificateBase64 { get; set; }
    public string PassTypeIdentifier { get; set; }
    public string PassbookCertificateBase64 { get; set; }
    public string PassbookPassword { get; set; }
    public string IconUrl { get; set; }
    public string LogoUrl { get; set; }

    public X509Certificate2 AppleWWDRCACertificate() =>
        new(Convert.FromBase64String(WWDRCertificateBase64));

    public X509Certificate2 PassbookCertificate() =>
        new(Convert.FromBase64String(PassbookCertificateBase64), PassbookPassword);

    public async Task<byte[]> GetLogo() => await LogoUrl.GetBytesAsync();

    public async Task<byte[]> GetIcon() => await IconUrl.GetBytesAsync();
}
```

## Distributing Wallet Pass Files Using .NET

To distribute the Wallet Pass Files generated, you can host this in an API endpoint and share the link via email, a button on a web page, or an app.

You can host this along with your other application code on the Cloud or your own servers.

For this post, I'll host this AWS Lambda, as it's a quick and easy way to host such kind of API endpoints. Using the latest Lambda Annotations Framework makes it even easier to create and host API endpoints.

%[https://www.rahulpnath.com/blog/lambda-annotation-framework-crud-api/] 

All we need to do is add the [Lambda Annotation Framework](https://www.rahulpnath.com/blog/aws-lambda-annotation-framework/) NuGet package and wrap our Pass Generation function with the `LambdaFunction` attribute. Since this is going to be exposed as an API will also add the `HttpApi` attribute and specify the route and the method `Get`.

```csharp
[LambdaFunction(Policies = "AWSLambdaBasicExecutionRole")]
[HttpApi(LambdaHttpMethod.Get, "/apple-wallet")]
public async Task<APIGatewayHttpApiV2ProxyResponse> GetAppleWalletPasses()
{
    var eventId = Guid.NewGuid();
    var eventName = "YouTube Demo Event";
    ...
    var generator = new PassGenerator();
    var requests = new List<PassGeneratorRequest>() { request };
    var pass = generator.Generate(requests);

    return new APIGatewayHttpApiV2ProxyResponse()
    {
        Body = Convert.ToBase64String(pass),
        IsBase64Encoded = true,
        StatusCode = 200,
        Headers = new Dictionary<string, string>
        {
            { "Content-Type", "application/vnd.apple.pkpasses" },
            { "Content-Disposition", "attachment; filename=tickets.pkpasses.zip; filename*=UTF-8''tickets.pkpasses.zip" }
        }
    };
}
```

The `HttpApi` attribute allows us to expose the Lambda Function over a [Lambda Function URL](https://www.rahulpnath.com/blog/function-urls-in-aws-lambda-dotnet/) or an [HTTP API Gateway endpoint](https://www.rahulpnath.com/blog/amazon-api-gateway-http-apis/).

Both of these mechanisms expect binary content to be encoded as base64 string, with the appropriate HTTP headers set. To achieve this we can use the `APIGatewayHttpApiV2ProxyResponse` object as shown above.

Once deployed, you can navigate the API Gateway URL endpoint or expose a Lambda Function URL, and navigate to the URL from an iPhone Safari browser, to add it to Apple Wallet.

Below is the screenshot of the pass generated using the above code, with the appropriate information we passed to create the pass.

![Apple Walled Pass in phone screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iip8ti6zn6hcutpdzuvu.png)

Apple Wallet Passes added to phone showing the front and the back details of the pass.

For debugging pass files you can use the [PKPassValidator service](https://pkpassvalidator.azurewebsites.net/), where you can upload a pass file and it will show any issues with the file generated.

The Apple Wallet pass generated above is static and does not update itself.

However most of the time you would need to deliver live updates to these passes in case any information changes for example date changes, venue changes, status updates, etc. For this, we can configure the `WebServiceUrl`, on the pass that it will poll for pass updates

We will learn how to configure and set up the CallbackUrl in a future blog post. Until then, happy coding! 👋