At 11pm the night before a launch, a developer we'll call Renata wires up the last piece of her Apple Wallet integration: the signature. She's followed a tutorial that links to a WWDR certificate download, exported her Pass Type ID certificate from Keychain Access as a .p12, and signed the manifest exactly as the guide describes. The phone's response is "The pass cannot be installed to Passbook at this time," with nothing else to go on. Two hours of guessing later, the actual problem turns out to be the WWDR file itself. The tutorial links to G2, and Apple Wallet only accepts G4. Everything else about her signature was correct.
That failure mode is common enough to be the single most-written-about obstacle in Apple Wallet development, ahead of anything in pass.json itself. Every path into Wallet runs through the same chain: an Apple Developer Program membership, a Pass Type ID certificate, Apple's WWDR intermediate, and a PKCS#7 signature that ties them together. Get any one piece wrong and Wallet's response is the same unhelpful line Renata saw, with no indication of which link in the chain actually failed.
Two certificates, one signature
A .pkpass file is a signed ZIP archive. Inside it sits pass.json with your fields and barcode, whatever images the pass needs, a manifest.json listing a hash of every file in the bundle, and a signature file proving nothing in that bundle has been altered since you produced it. Wallet refuses to install the archive unless the signature checks out against a certificate chain it trusts.
That chain needs two certificates, not one, because they prove two different things. Your Pass Type ID certificate proves you, specifically, are authorized to issue passes under a given identifier such as pass.com.example.loyalty. Apple's WWDR intermediate proves that your certificate was actually issued by Apple, by chaining it back to a root certificate iOS already trusts. A signature built from your certificate alone has no way to convince a phone it came from a real developer account rather than something self-signed five minutes ago. Bundling the WWDR certificate alongside the signature is what lets Wallet walk that chain and confirm both links.
The two certificates also age on different schedules, which is easy to miss until it causes a problem. Your Pass Type ID certificate is yours to renew every year. The WWDR intermediate is Apple's to maintain, and it changes rarely enough that most teams download it once and never think about it again, which is exactly why a stale or wrong copy of it is so easy to leave sitting in a repository for years without anyone noticing.
Getting a Pass Type ID certificate that will actually pass
The chain starts with an Apple Developer Program membership, which costs $99 a year whether you issue ten passes or ten million. From Certificates, Identifiers & Profiles in the developer portal, you register a new Pass Type ID under the Identifiers section, a reverse-domain string such as pass.com.example.loyalty that becomes the passTypeIdentifier field in every pass you sign under it.
Getting the certificate itself means generating a certificate signing request first, since Apple issues a certificate against a key pair you already hold rather than handing you one outright:
openssl req -new -newkey rsa:2048 -nodes \
-keyout passcert.key \
-out passcert.csr \
-subj "/emailAddress=you@example.com, CN=Pass Type ID Certificate, C=US"Upload passcert.csr against your new Pass Type ID, and Apple hands back a .cer file. Import that into Keychain Access alongside passcert.key, then export the pair together as a single .p12, which is the bundle your signing code actually reads. Miss the private key on export and you'll have a valid-looking certificate that can't sign anything.
One detail trips up more integrations than the steps themselves. The certificate's Organizational Unit field carries your Apple Developer Team ID, and that value has to match the teamIdentifier field in every pass.json you sign with it exactly. A mismatch here produces the same generic install failure as a bad WWDR file, with nothing in the error pointing at the actual cause.
None of this technically requires a Mac. The CSR, the signing, and the manifest hashing are all plain OpenSSL and can run on any Linux box or in CI. What does assume a Mac is the tutorial ecosystem around it, since most guides walk through Keychain Access rather than the command line equivalent, which is why teams building this on a Linux server so often end up hand-translating a set of Keychain screenshots into openssl flags before they get anywhere.
The one WWDR certificate that works
Apple has issued several generations of the WWDR intermediate certificate over the years, labeled G2 through G6, mostly to segment which service each one covers and to keep certificate revocation lists manageable. For Apple Wallet passes specifically, only one of them works. Pass Type ID certificates chain to AppleWWDRCAG4, and G2, G3, G5, and G6 all fail validation even though nothing about the download or import process looks any different.
That G4 certificate has been valid since December 16, 2020, and doesn't expire until December 10, 2030, so once you've downloaded the correct one you can treat it as a fixture rather than something to track. The trouble is almost never that G4 has expired. It's that a search result, a Stack Overflow answer, or an older tutorial links to a different generation entirely, and the failure it produces gives no hint that the WWDR file was the problem at all.
What the signature is actually protecting
The manifest and signature exist to catch tampering, and the mechanics are worth knowing because they explain why certain mistakes fail loudly while others fail silently. Building manifest.json means walking every file in the pass bundle except the manifest and signature themselves, computing a SHA-1 hash of each one's contents, and storing those hashes keyed by relative path. signature is a detached PKCS#7 blob built over that manifest, using your Pass Type ID certificate and private key, with the WWDR certificate attached so the whole chain travels with the pass.
When a phone opens a .pkpass, it recomputes the SHA-1 hash of every file and compares each one against what manifest.json claims, which is what catches a corrupted image or a hand-edited pass.json that never got re-signed. Only after every hash matches does it verify the PKCS#7 signature itself and walk the attached WWDR certificate back to Apple's trusted root. A single byte changed anywhere in the bundle after signing, without re-signing afterward, fails the first check before the certificate chain is ever examined.
What actually breaks when the certificate expires
A Pass Type ID certificate is valid for one year, and it isn't renewable in place, only replaceable. Apple typically emails a reminder around 30 days out, but nothing in the system stops a team from missing it, since the certificate keeps working right up until it doesn't, with no gradual warning visible to the people building on top of it.
When it does lapse, the effect is narrower than it sounds and easy to misjudge in either direction. Passes already installed on a customer's phone keep working exactly as they are. The barcode still scans, the fields still display, and nothing about the expired certificate reaches back out to devices that already have a pass. What stops is anything that requires a fresh signature: you can no longer issue a new pass to a new customer, and you can no longer push an update to a pass someone already holds, since every update is really a re-signed pass fetched over the same web service protocol as the original install. A loyalty program can go weeks without anyone noticing, right up until a customer's points balance stops updating and support has no explanation to offer.
Recovering means creating a brand new certificate under the same Pass Type ID, the CSR-and-export process again from scratch, and swapping it into whatever signs your passes. The identifier stays the same, so already-issued passes don't need to be reissued once the new certificate is in place. What has to happen is catching the expiration before a customer does, which in practice means tracking a date on a calendar that Apple's own portal won't surface for you.
Revoked is worse than expired
Expiration and revocation get conflated constantly, and the gap between them matters more than either term on its own suggests. An expired certificate is a scheduling failure. It stops you from signing anything new, but every pass you already issued keeps working exactly as it did the day it was installed. Revocation is a different category of problem. Apple's own guidance is that a revoked certificate can stop already-issued passes from functioning properly, not just your ability to sign new ones, which makes it the one action here worth treating as effectively irreversible.
Revocation isn't something Apple does to you on a timer. It happens when someone in Certificates, Identifiers & Profiles clicks revoke, usually while cleaning up a list of certificates that has grown unreadable after a few years of expirations nobody removed. A departing engineer's old signing certificate and the one currently in production can look identical in that list if the naming wasn't disciplined from day one, and revoking the wrong row turns a routine cleanup into an incident that reaches live devices. Treat revocation as a one-way, blast-radius action reserved for a certificate you're certain is compromised, and leave merely expired ones alone rather than tidying them away.
Reading a generic failure backward
"The pass cannot be installed to Passbook at this time" is the entire error message Wallet gives you, regardless of which piece of the chain caused it, so debugging one means working backward from the symptom to the cause instead of the other way around. A few patterns show up often enough to check first, in roughly the order they're worth ruling out:
- A pass fails on every device, immediately. Almost always the WWDR certificate. Confirm it's specifically
AppleWWDRCAG4and not a copy pulled from an old repository or a tutorial written before 2022. - A pass fails and the Team ID in
pass.jsonwas typed by hand. Compare it character for character against the Organizational Unit on the signing certificate rather than trusting memory, since the two are supposed to be identical and a single wrong digit produces no clearer error than a missing certificate. - Signing succeeds locally but the
.p12behaves differently in production. Check whether the export actually included the private key. A.p12built from the public certificate alone imports without complaint and fails only once something tries to sign with it. - A pass that worked yesterday fails today with no code changes. Check the Pass Type ID certificate's expiration date before anything else. It's the one part of the chain that changes on its own, silently, once a year.
None of these produce a distinct error on the device. They produce the same line every time, which is why knowing the chain in advance is worth more than trying to debug it from Wallet's side of the failure.
Where this leaves a team running this themselves
Add it up: an annual certificate with no built-in renewal, a WWDR download where four of five available versions quietly fail, a Team ID field that has to match a certificate field exactly, and a signature format that catches tampering but tells you nothing about which link in the chain actually broke when installation fails. None of it is difficult in isolation. All of it has to be gotten right at once, with an error message that never says which piece was wrong.
If you'd rather not own that renewal calendar, Passmint's managed certificates handle the Pass Type ID chain, the correct WWDR version, and the yearly rotation, so a signed pass is an API call rather than a Keychain Access session at 11pm.
Primary sources
Common questions
Technical content writer, Passmint
Julio is a technical content writer at Passmint. He writes about Apple PassKit, the Google Wallet API, and what breaks when wallet passes meet production traffic.
More from Julio Song →