Fastlane integration — auto-share every IPA build
What's in this guide
If your iOS pipeline already runs Fastlane, you're 90% of the way to having every build automatically shared with your testers. This guide walks through adding the App On The Go action so that the moment gym finishes building an IPA, your testers get a fresh install link in their Slack channel.
Why integrate with Fastlane
- No manual uploads. Every commit on your beta branch produces a sharable URL. No more "uploading the build now" Slack messages.
- Stable testing URL. The action can keep the same URL across builds — testers bookmark it once and always get the latest version.
- Same code path on every CI. GitHub Actions, Bitrise, CircleCI, Xcode Cloud, Jenkins, your local machine — Fastlane abstracts over all of them. You write the lane once.
- Automatic metadata. Version, build number, bundle ID, icon are all pulled from the IPA's
Info.plist— the install page is filled in for you.
1. Generate an API token
In your App On The Go dashboard, open Settings → API tokens and click Create token. Give it a name like "CI" so you can revoke it later if it leaks.
Tokens carry the same permissions as your account, scoped to upload + read. Treat them like passwords.
Store the token in your CI provider as a secret named AOTG_TOKEN. Every CI we've ever seen has a way to inject secrets as environment variables; the action reads from there automatically.
2. Install the Fastlane plugin
Inside your iOS project's fastlane directory, run:
fastlane add_plugin aotg
This creates (or updates) fastlane/Pluginfile with the dependency. Commit that file along with your Gemfile.lock so CI installs the same version.
3. Wire it into your Fastfile
Find your existing beta or ad-hoc lane (most teams call it :beta or :adhoc). It probably looks something like:
lane :beta do
build_app(scheme: "App", export_method: "ad-hoc")
end
Add the aotg action after build_app:
lane :beta do
build_app(scheme: "App", export_method: "ad-hoc")
aotg(
file: lane_context[SharedValues::IPA_OUTPUT_PATH],
token: ENV["AOTG_TOKEN"],
expiry_days: 30,
notify_slack: true
)
end
What each parameter does:
file:— path to the IPA.build_appstores it inlane_context[SharedValues::IPA_OUTPUT_PATH]automatically.token:— the API token from step 1.expiry_days:— how long the share link stays live. Default 30 days; Pro plan supports up to 365.notify_slack:— if true, posts the install link to the Slack channel configured in your account settings. Free tier shares the URL via webhook; Pro adds Slack.
4. Run it
Locally first, to verify everything works end-to-end:
export AOTG_TOKEN=your_token_here
fastlane beta
Fastlane builds the IPA, the aotg action uploads it, and prints the install URL at the end of the run:
[16:21:42]: Upload complete (12.4 MB in 3.1s)
[16:21:42]: Install URL: https://i.apponthego.com/AbCdE
[16:21:42]: QR code: https://i.apponthego.com/AbCdE.png
From here, push your changes; your CI will pick up the same lane on every build to your default branch.
Optional: Slack / webhook notifications
If you don't want to set notify_slack: true in the Fastfile, you can have the action post a generic webhook that any chat tool can consume:
aotg(
file: lane_context[SharedValues::IPA_OUTPUT_PATH],
token: ENV["AOTG_TOKEN"],
webhook_url: ENV["AOTG_WEBHOOK"]
)
The payload is a small JSON blob: { url, version, build, app_name, qr_url }. Wire it into Slack incoming webhooks, Microsoft Teams, Discord, or whatever your team uses.
In GitHub Actions / Bitrise / Xcode Cloud
Same Fastlane lane, just wrapped in a CI workflow.
GitHub Actions
name: iOS beta
on:
push:
branches: [main]
jobs:
beta:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Build & share
env:
AOTG_TOKEN: ${{ secrets.AOTG_TOKEN }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
run: bundle exec fastlane beta
Bitrise
Add a "Run fastlane" step to your workflow. Inject AOTG_TOKEN as a secret env. That's it — same Fastfile, no Bitrise-specific changes.
Xcode Cloud
Xcode Cloud doesn't run Fastlane natively, but you can call it from a post-build script (ci_post_xcodebuild.sh) that invokes the same lane. Same secrets injection model.
Troubleshooting
"401 Unauthorized" from the action
- Token isn't in the environment. Most common cause on CI: secrets aren't passed to the job. Check your workflow's env block.
- Token was revoked. Generate a new one in dashboard → API tokens.
"IPA not found" / "File not readable"
- Build failed earlier in the lane and there's no IPA. Check the
build_appoutput above this error. build_appwrote to a different path than the default. Pass the path explicitly:aotg(file: "./build/MyApp.ipa", ...)
"File exceeds plan limit"
- Free tier caps at 100 MB without signup. Sign in (Starter is 1 GB, Pro is 5 GB).
- Heavy assets bloating the build? Analyse the IPA first; embedded frameworks are often the culprit.
The lane succeeds but testers report "app won't launch"
- The tester's UDID isn't on your ad-hoc provisioning profile. Decode the embedded profile with our .mobileprovision decoder and confirm.
- Profile expired. Profiles last 1 year max — same decoder shows the expiration date.
Related reading
- How to install an IPA over the air — what testers actually see on the other side.
- How to share an APK with testers — the Android counterpart workflow.
- IPA analyzer — inspect the IPA Fastlane produced before shipping it.
- .mobileprovision decoder — debug "won't launch" errors.