Will this IPA install? Six checks to run before you send a link
· 7 min read
The most common test build problem is not a crash. It is a build that never installs. The tester taps Install, the icon appears, and then the iPhone says the app could not be installed, or the integrity of the app could not be verified. By then the link is out, the tester is waiting and you are rebuilding.
Almost every one of those failures can be seen in the .ipa before you share it. The file carries its provisioning profile, its signing certificate and its entitlements, and an iPhone checks the same things when it installs. Below are the six checks that catch nearly all of them, how to run each by hand on a Mac, and how to run all of them in one command. A short Android section is at the end. Facts about AppForShare, the tool we make, are as of November 9, 2026.
First, open the IPA
An .ipa is a zip file. Unzip it and pull out the embedded profile as a readable plist:
unzip -q MyApp.ipa -d ipa
security cms -D -i ipa/Payload/MyApp.app/embedded.mobileprovision > profile.plist
Most checks below read profile.plist with PlistBuddy, which ships with macOS at /usr/libexec/PlistBuddy.
Check 1: is there a profile, and what kind is it?
If embedded.mobileprovision is missing, the build was not signed for devices (often a simulator build or an unsigned archive) and will not install from a link. If it is there, its contents tell you the kind:
| Profile contains | Kind | Installs from a link? |
|---|---|---|
ProvisionedDevices and get-task-allow true | Development | Yes, on listed devices |
ProvisionedDevices and get-task-allow false | Ad Hoc | Yes, on listed devices |
ProvisionsAllDevices true | Enterprise | Yes, for the organisation's own staff |
| Neither | App Store | No, only through TestFlight or the App Store |
An App Store signed build is the classic mistake: the archive was exported for App Store Connect, and the file looks fine but no iPhone will take it from a link.
Check 2: has the profile expired?
/usr/libexec/PlistBuddy -c 'Print :ExpirationDate' profile.plist
Ad Hoc and Development profiles last up to a year. An expired profile means the app will not install, and an app already installed will stop opening. Profiles that end in the next week or two are worth renewing before you send a build that testers will keep.
Check 3: is the tester's device in the profile?
/usr/libexec/PlistBuddy -c 'Print :ProvisionedDevices' profile.plist | grep -i 00008110-001A2B3C4D5E6F70
Replace the value with the tester's UDID. No match means that iPhone will refuse the install. The fix is to register the device in the Apple Developer portal, regenerate the profile and build again. A tester can find their UDID without a cable on our UDID finder. The new profile only helps after the rebuild; the old file keeps the old list.
Check 4: does the profile cover the bundle ID?
/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' ipa/Payload/MyApp.app/Info.plist
/usr/libexec/PlistBuddy -c 'Print :Entitlements:application-identifier' profile.plist
The second value is the team ID, a dot and the App ID, such as ABCDE12345.com.example.myapp or a wildcard like ABCDE12345.com.example.*. The bundle ID must match it. Also check app extensions under Payload/MyApp.app/PlugIns: each extension has its own bundle ID and its own embedded profile, and one bad extension is enough to fail the whole install.
Check 5: does the profile grant every entitlement the app asks for?
codesign -d --entitlements - ipa/Payload/MyApp.app
/usr/libexec/PlistBuddy -c 'Print :Entitlements' profile.plist
Compare the two lists. Every entitlement the app is signed with, such as aps-environment for push, associated domains, app groups or keychain groups, must be allowed by the profile. A capability added in Xcode after the profile was made is the usual cause. A wildcard App ID cannot carry push or associated domains at all, so a build that needs them must use an explicit App ID.
Check 6: is the signing certificate still valid?
codesign -dvv ipa/Payload/MyApp.app 2>&1 | grep Authority
codesign -d --extract-certificates ipa/Payload/MyApp.app
openssl x509 -inform DER -in codesign0 -noout -subject -enddate
The first line shows which certificate signed the app, for example "Apple Distribution: Example Ltd (ABCDE12345)". It must be one of the certificates listed in the profile under DeveloperCertificates. The last command prints when the certificate ends. An expired certificate gives the same "could not be installed" result as an expired profile. Our post on "integrity could not be verified" covers what the tester sees for each of these.
Run all six at once
Doing this by hand for every build gets old fast. Two ways to do it in one step:
In the browser
Drop the .ipa into the IPA inspector to see the distribution type, profile expiry with days left, certificates, the full UDID list with search and the entitlements. The provisioning profile inspector reads a .mobileprovision together with the .ipa and lists why a device would refuse the install: expired profile, App Store signing, UDID not in the profile, bundle ID not covered, entitlements the profile does not grant, or a wildcard App ID with push or associated domains. Both read the file in your browser; nothing is uploaded, and no account is needed. They read and explain; they do not fix or re-sign anything.
On the command line
npx appforshare check build/MyApp.ipa
npx appforshare check build/ --fail-on review --json
The check reads the file on your computer and sends it nowhere. It runs the install checks above (including a profile that ends within 14 days), the same store readiness rules as the inspectors, and the security scan rules. The readiness and install checks need no account. The security scan findings themselves need a Pro or Team token; without one you get the counts only. Exit codes are 0 when it passes, 1 when the check could not run and 2 when the result is blocked. With --fail-on review it also exits 2 on review. App Store signed files are not failed for install problems, because they are meant for TestFlight.
In GitHub Actions, the AppForShare action takes check: true and fails the step before anything is uploaded. The checks catch problems inside the file; they cannot tell whether Apple has since revoked a certificate, and they are not a promise of store approval. See check a build before upload for details.
Android: four checks for an APK
Android is more forgiving, but a few things still stop installs or cause trouble later.
- Signed at all. An unsigned APK will not install.
apksigner verify --verbose app.apkshows which signature schemes are present. - Test only. Android Studio's Run button can produce an APK with
android:testOnly="true". Phones refuse it from a browser download; onlyadb install -ttakes it. - Only a v1 signature on a recent target. An app that targets API 30 or higher must carry a v2 or newer signature to install on Android 11 and later. A v1 only signature on such an app fails.
- Debuggable or debug key. These install, but a debuggable build or one signed with the debug key is not what testers should get, and it will not update to your release build without an uninstall.
The APK analyzer shows all four in the browser, and npx appforshare check app.apk runs them on the command line.
Common questions
Why does a build install on my iPhone but not on the tester's?
Almost always check 3: your device is in the profile and theirs is not. Register their UDID, regenerate the profile and build again.
Does the check upload my build?
No. Both the browser tools and npx appforshare check read the file locally. Only the optional "Share this build with testers" button uploads it.
Can an App Store signed IPA be installed from a link?
No. Send it to TestFlight, or export the archive again with an Ad Hoc profile for link testing. Our guide to iOS code signing errors walks through the export.
Do I need an account to run the checks?
Not for the readiness and install checks. Security scan findings need a Pro or Team token.
Run the six checks once and they become a habit. When the build passes, create a free account to share it with an install link and a QR code.


