Alexsander Akers

Swiftly Passcards

May 15, 2017

A sepia-tone image of an old, tattered business card. It reads: Yours for Business and Success; Charles M. Wright, Expert Pension and Claim Attorney; Fifteen Years' Experience; Best of References; Wapello, Iowa

According to the Online Etymology Dictionary, the first attested use of “business card” dates back to 1840, a time when a paper representation of one’s contact details was the only possibility.

Business cards serve to facilitate the transfer of contact information. A conventional business card could contain, among other things, one’s name, email, website, phone number, and some assortment of social networks.

Today, almost 200 years since the first business cards, a frozen-in-time “snapshot” of personal information becomes immediately outdated as soon as it is printed. An ideal business card solution would live digitally in the cloud or on my phone, be easily shareable, and update itself when a person changes their contact details.

The Apple Wallet app (formerly Passbook) comes preinstalled since iOS 6. It’s designed to contain wallet-related tickets or cards, such as boarding passes, transit tickets, coupons, concert tickets, and, more recently, payment cards. These records can be updated remotely from a controlling server, such as when a ticket’s time changes, a gift card’s balance changes, or a boarding pass’s gate is announced.

Because the functionality to update was a requirement for my self-refreshing business cards, I thought to piggyback on Apple’s work to create a record in Wallet (or “pass” as they are called) that looked and behaved like a business card. In the same way that an airline can push a gate-change update to my phone, I can push a change of information (such as a new phone number) to people’s devices through the Apple Push Notification Service (APNS) which directs these devices to query the server for updates. An update is downloaded if found, and a visual indication of this update can be optionally displayed: “Your flight will depart from gate 65.”

Note that passes don’t need to be “dynamic”, or server-updatable. A pass representing a coupon, for example, might be “static”. Though because it conveys an offer applicable under certain conditions, it could benefit from an update when the offer is no longer valid. The difference at a technical level is the presence of a key-value pair in the pass’s JSON payload that direct a device to register itself as an update recipient. The value of this pair is a URL to which the device appends certain endpoints, as specified by the PassKit Web Service reference.

When I started this project years ago, I concentrated my efforts to write a web service implementation on Parse, which bundles a MongoDB instance, push notification support (via APNS or the-Google-equivalent-of-APNS), and cloud functions (custom JavaScript that runs on Parse’s servers). The best part was that, for my use case, these services were for free for my use case. When Parse announced it would shut down in early 2017, I wanted to write a new implementation in Swift to handle the pass client registration and the sending of push notifications.

Just what is a Pass, anyway?

A pass is a ZIP archive containing the following files:

  • pass.json The pass payload in JSON format. More information about its structure can be found in the PassKit Package Format Reference.
  • manifest.json A JSON object keyed by file path (relative to bundle root) and whose values are SHA-1 digest hashes of those files. All files must be listed, except manifest.json and signature.
  • signature A detached PKCS#7 signature of manifest.json, signed with your Pass Type ID certificate.
  • Optional images such as background.png, footer.png, icon.png, logo.png, strip.png, thumbnail.png. The images supported by a pass is dependent on the pass’s type.
  • Localized resources organized into *.lproj directories, not unlike in Apple platform apps.

The manifest.json file contains cryptographic digest “fingerprints” of every file in the pass, and the signature file is a cryptographic signature that verifies the pass’s authenticity. The signature is “detached” in that it itself does not contain the signed data. The abbreviation PKCS stands for Public Key Cryptography Standards (hopefully self-explanatory) developed by RSA Security Inc. The signing algorithm uses the seventh such standard and is formally defined by RFC 2315 (Cryptographic Message Syntax).

Generating this file is handled by the Apple’s signpass sample code. You can download the Wallet Support Materials here, which also include example passes and sample Ruby code for signing and serving passes.

Picking a Pass Style

A pass’s style is specified by the presence of exactly one of the following keys: boardingPass, coupon, eventTicket, generic, and storeCard.

boarding pass coupon generic event ticket store card
boardingPass coupon generic eventTicket storeCard

For a business card, the boardingPass and eventTicket styles do not make so much sense, but can be useful for other occasions. The storeCard and coupon styles are very similar, with the coupon style having a slight ripped border along the top but have otherwise the same features. A strip.png image can provide a background, as seen in the “Organic Produce” storeCard example. The generic type is the only one that supports the thumbnail.png image, which makes it great for a photo of the person’s face, though the storeCard’s strip.png can provide a image, albeit longer.

Making My Pass

My pass’s JSON payload, with added comments and keys reordered and grouped for clarity:

  // must be 1
  "formatVersion": 1,

  // required for "dynamic" passes
  "authenticationToken": "<secret>",
  "serialNumber": "<secret>",
  "webServiceURL": "",

  // top-left "logo" text
  "logoText": "Alexsander Akers",

  // barcode data, handy for scanning with the Wallet app
  "barcode": {
    // PDF417 is a cool format but you can use QR or Aztec
    "format": "PKBarcodeFormatPDF417",
    "message": "",
    "messageEncoding": "iso-8859-1"

  // optional metadata
  "description": "Alexsander Akers",
  "organizationName": "Alexsander Akers",

  // style colors as rgb strings
  "backgroundColor": "rgb(0, 168, 143)", // #00a88f
  "foregroundColor": "rgb(255, 255, 255)", // white
  "labelColor": "rgb(255, 255, 255)", // white

  // style key
  "storeCard": {
    // secondary fields are below the strip image
    "secondaryFields": [
      { "key": "email", "label": "Email", "value": "" },
      { "key": "twitter", "label": "Twitter", "value": "@a2" }
    // back fields, interface revealed with (i) button
    "backFields": [
      { "key": "email-back", "label": "Email", "value": "" },
      { "key": "facebook", "label": "Facebook", "value": "" },
      { "key": "github", "label": "GitHub", "value": "" },
      { "key": "website", "label": "Website", "value": "" }

  // signing data
  "passTypeIdentifier": "<secret>",
  "teamIdentifier": "<secret>"

For each field (whether in primaryFields, secondaryFields, auxiliaryFields, headerFields or backFields), the key key is required and must be unique in the pass. A value is also required, but can be localized (translated) through a locale-specific Localizable.strings file in an *.lproj subdirectory (e.g. enUS.lproj_ for American English).

For example, you can override a value with “Hello, world!” in the English Localizable.strings file and “Hallo, Welt!” in the German Localizable.strings file, and so on. Number formatting is available through optional numberStyle and currencyCode, and date formatting through the dateStyle, ignoresTimeZone, isRelative, and timeStyle keys. More information about that can be found on the Field Dictionary Keys page.

After the payload is written, we get to the images. I included icon.png, logo.png, and strip.png images in three sizes. That is, icon.png as well as icon@2x.png and icon@3x.png; the same applies for logo.png and strip.png.

Uploading to Passcards

Once you’ve done that, it’s time to sign the pass, using the signpass utility from Apple (unless you’re feeling adventurous). Note that you can double click the resulting *.pkass file on your Mac for a preview, although the UI is relatively outdated. Then you can upload the pass to Passcards with this cURL command et voilà!

Alexsander Akers

I work in Berlin on Microsoft To Do for Apple platforms and occasionally speak at meet-ups and conferences. I'm very enthusiastic about pandas. You can follow me on Twitter.