Every over-the-air install of a test build, whatever tool hosts it, comes down to two files on a web server and one special link.
The link
<a href="itms-services://?action=download-manifest&url=https://example.com/manifest.plist">Install App</a>
When someone taps it in Safari on an iPhone, iOS downloads the manifest, asks to install the app, then downloads and installs the .ipa the manifest points to. Don't link to the .ipa directly. iOS won't install it that way.
The manifest
The manifest is a small XML property list. This is the shape of Apple's own example:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://example.com/MyApp.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>url</key>
<string>https://example.com/icon.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.example.myapp</string>
<key>bundle-version</key>
<string>1.2</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>My App</string>
</dict>
</dict>
</array>
</dict>
</plist>- software-package is the .ipa's URL.
- display-image is the icon shown while the app installs.
- bundle-identifier must be the app's real bundle ID. Don't use a wildcard.
- bundle-version is the app's version.
- title is the name shown in the install prompt.
The server rules
- HTTPS everywhere. The manifest URL and the .ipa must both use HTTPS, with a certificate the iPhone trusts. A self-signed certificate makes the install fail.
- MIME types. Apple suggests serving
.ipaasapplication/octet-streamand.plistastext/xml. - Open it in Safari. In-app browsers, such as the ones in Gmail or Slack, don't hand the link to iOS, so nothing happens.
The link doesn't sign anything
The manifest only tells iOS where the build is. The .ipa still has to be signed for the iPhone: an ad hoc or development build that lists its UDID, or an enterprise build. Otherwise you'll see "Unable to Install" or "integrity could not be verified". Check which devices a build includes.
Hosting it yourself vs a tool
Hosting it yourself works well if you already run an HTTPS server and your testers' iPhones are already in the profile. You write the manifest for each build and keep the files up to date.
Quick share does the hosting for you: drop a signed .ipa and get the link and a QR code, free, with no account for 24 hours.
When new testers keep joining, the slow part isn't the link, it's re-signing for each new iPhone. BuildFerry registers each iPhone you approve with your Apple account and re-signs the build, behind one link that never changes. Start free.
Questions
What is an itms-services link?
A link starting with itms-services://?action=download-manifest&url= that points to a manifest.plist over HTTPS. Opened in Safari on an iPhone, it installs the app the manifest describes.
Why does my itms-services link do nothing?
Usually because it's open in another app's browser. Open it in Safari. Also check the manifest and .ipa are on HTTPS with a trusted certificate.
Does the manifest need the right bundle identifier?
Yes. Use the app's exact bundle ID in bundle-identifier, not a wildcard.
Can an itms-services link install any IPA?
No. It only delivers the file. The .ipa must be signed for that iPhone, with an ad hoc or development profile that lists it, or an enterprise profile.