Server-side Wallet Pass pkpass generation and update for mobile app

BLACKSPARC.TECH is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Server-side Wallet Pass pkpass generation and update for mobile app
Medium
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    760
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    640
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1056
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    878
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    449

Generating and Updating Wallet Pass (pkpass) on the Server for Mobile Apps

A .pkpass file is a ZIP archive containing JSON description, images, and signature. Apple Wallet only accepts passes signed with a certificate from the Apple Developer Program. This is a server-side operation: the certificate is stored on the backend, the client receives the ready .pkpass file and opens it.

pkpass Structure

pass.pkpass/
├── pass.json          # pass description
├── manifest.json      # SHA1 hashes of all files
├── signature          # PKCS#7 signature of manifest.json
├── icon.png           # 29x29 pt
├── [email protected]        # 58x58 pt
├── [email protected]        # 87x87 pt
├── logo.png           # up to 160x50 pt
└── background.png     # optional, boardingPass only

pass.json: Key Fields

{
  "formatVersion": 1,
  "passTypeIdentifier": "pass.com.yourcompany.membercard",
  "serialNumber": "user-12345-2024",
  "teamIdentifier": "ABCD1234EF",
  "organizationName": "Your Company",
  "description": "Member Card",
  "foregroundColor": "rgb(255, 255, 255)",
  "backgroundColor": "rgb(15, 82, 186)",
  "storeCard": {
    "primaryFields": [
      {
        "key": "member_name",
        "label": "Member",
        "value": "Ivan Petrov"
      }
    ],
    "secondaryFields": [
      {
        "key": "points",
        "label": "Points",
        "value": "1 250",
        "changeMessage": "Points updated: %@"
      }
    ],
    "auxiliaryFields": [
      {
        "key": "tier",
        "label": "Status",
        "value": "Gold"
      }
    ],
    "backFields": [
      {
        "key": "terms",
        "label": "Terms",
        "value": "Points valid 12 months from accrual."
      }
    ]
  },
  "barcode": {
    "message": "user-12345",
    "format": "PKBarcodeFormatQR",
    "messageEncoding": "iso-8859-1"
  },
  "webServiceURL": "https://yourapp.com/wallet/",
  "authenticationToken": "vxwxd7J8AlNNFPS8k0a0FfUFtq0ewzV"
}

The webServiceURL + authenticationToken fields provide the push-update mechanism. Apple Wallet will register the device on your server and request updated pass when changes occur.

Server-Side Generation in Python

import hashlib
import json
import zipfile
import io
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from cryptography.hazmat.primitives.serialization import pkcs7

def generate_pkpass(pass_data: dict, images: dict[str, bytes]) -> bytes:
    # 1. Serialize pass.json
    pass_json = json.dumps(pass_data, ensure_ascii=False).encode('utf-8')

    # 2. Build manifest — SHA1 of all files
    manifest = {}
    files = {"pass.json": pass_json, **images}
    for filename, content in files.items():
        manifest[filename] = hashlib.sha1(content).hexdigest()

    manifest_json = json.dumps(manifest).encode('utf-8')

    # 3. Sign manifest with PKCS#7 detached signature
    with open("pass-cert.p12", "rb") as f:
        p12_data = f.read()

    private_key, certificate, chain = pkcs12.load_key_and_certificates(
        p12_data, b"p12_password", default_backend()
    )

    # Load Apple WWDR intermediate certificate
    with open("AppleWWDRCA.cer", "rb") as f:
        wwdr_cert = x509.load_der_x509_certificate(f.read(), default_backend())

    signature = pkcs7.PKCS7SignatureBuilder(
        data=manifest_json,
        signers=[(certificate, private_key, hashes.SHA256())]
    ).add_certificate(wwdr_cert).sign(
        encoding=serialization.Encoding.DER,
        options=[pkcs7.PKCS7Options.DetachedSignature]
    )

    # 4. Pack into ZIP
    buffer = io.BytesIO()
    with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
        zf.writestr("pass.json", pass_json)
        zf.writestr("manifest.json", manifest_json)
        zf.writestr("signature", signature)
        for filename, content in images.items():
            zf.writestr(filename, content)

    return buffer.getvalue()

The Pass Type ID certificate is created in Apple Developer Portal → Certificates, Identifiers & Profiles → Identifiers → Pass Type IDs.

Download Endpoint

@app.get("/wallet/pass/{user_id}")
async def download_pass(user_id: str, token: str = Query(...)):
    user = db.get_user(user_id)
    if not verify_token(user, token):
        raise HTTPException(403)

    pass_data = build_pass_json(user)
    images = load_pass_images()
    pkpass_bytes = generate_pkpass(pass_data, images)

    return Response(
        content=pkpass_bytes,
        media_type="application/vnd.apple.pkpass",
        headers={"Content-Disposition": f"attachment; filename={user_id}.pkpass"}
    )

iOS: Opening pkpass

import PassKit

func downloadAndAddPass(url: URL) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        guard let data, error == nil else { return }

        do {
            let pass = try PKPass(data: data)
            DispatchQueue.main.async {
                let vc = PKAddPassesViewController(pass: pass)!
                self.present(vc, animated: true)
            }
        } catch {
            print("Invalid pass: \(error)")
        }
    }.resume()
}

Push Updates via APNs

When data changes (points issued, status changed), the server:

  1. Gets the device's pushToken from the database (Apple Wallet registered it when the pass was added)
  2. Sends an empty push via APNs to the passkit production endpoint
  3. Wallet makes GET /wallet/v1/passes/{passTypeIdentifier}/{serialNumber} with Bearer token

The response is an updated .pkpass. Wallet applies changes and displays changeMessage if set in pass.json.

Timeline

2–3 days. Server-side generation with signature, download endpoint, iOS integration — 2 days. Push updates via APNs — additionally 0.5–1 day. Pricing is calculated individually.