Stop blocking your CI on App Store Connect build processing
fastlane's default upload behaviour blocks a CI runner for 5–25 minutes polling Apple while a build processes. Here's the one-line change to remove the wait — and the webhook setup that still notifies your team (Slack, Teams, Discord, PagerDuty, email, or any HTTP endpoint).
fastlane CI App Store Connect iOS
Every iOS team with CI has a fastlane lane that ends like this:
lane :beta do
build_app(scheme: "MyApp")
upload_to_testflight
end
And every iOS team with CI eventually notices that upload_to_testflight is, oddly, the slow part. The build itself takes a few minutes; the upload to App Store Connect takes a minute; and then the lane sits there for twenty minutes, watching paint dry, while App Store Connect processes the build server-side.
That whole twenty minutes is happening on a CI runner that’s not doing anything except polling Apple. If you’re on GitHub Actions, that’s a macOS runner billed at ten times the rate of a Linux runner. If you have a self-hosted Mac mini, it’s a slot in your concurrency limit. Either way, it’s bottlenecking your pipeline for no reason.
This post is about the one-line fastlane change that removes that wait, and the webhook that fills in the gap so you still get a Slack notification when the build is actually ready.
What’s actually happening
The upload_to_testflight action does roughly:
- Authenticate to App Store Connect.
- Upload the IPA via the standard upload endpoint.
- Poll the App Store Connect API every 30 seconds (fastlane’s default
wait_processing_interval) for the build’s processing state. - Return when the build state transitions to
VALID(orFAILED/INVALID). - Optionally hand the build off to TestFlight for distribution.
Step 3 is the one that blocks. Apple’s processing typically takes between five and twenty-five minutes, sometimes longer during service degradation. The fastlane action sits in a polling loop until the build is done. Your runner is doing nothing useful — sleeping on IO.select, mostly — but it’s allocated to the lane and can’t be released until processing finishes.
A typical breakdown for a TestFlight pipeline:
| Step | Time |
|---|---|
| Checkout + cache restore | ~30s |
xcodebuild archive |
3–10 min |
| Upload IPA to ASC | 30s–1 min |
upload_to_testflight waiting for processing |
5–25 min |
| Total | 9–37 min |
The processing wait is often the longest single step. And it’s the one step where no actual work is happening on your runner.
The cost
Three flavours:
- Money, if you care at this scale: on GitHub Actions, macOS minutes are billed at ten times the rate of Linux minutes. A team shipping 20 TestFlight builds a week pays for 200+ minutes of pure polling. The dollar figure is small for one team, but it adds up across the year and across multiple apps.
- Concurrency: standard GitHub plans give you a handful of concurrent macOS runners. If you ship 5 PRs in quick succession and each blocks for 20+ minutes waiting on ASC, your queue backs up. The 6th PR has to wait for one of the first five to finish polling before it even starts building. This compounds on busy days.
- Mental model: developers expect “push → CI runs → Slack notification” to be a few minutes. The actual cycle is 20–40 minutes. People context-switch, lose the thread, come back later. Code review velocity slows even when nothing is technically broken.
The one-line fix
fastlane already has the escape hatch. Tell it not to wait:
lane :beta do
build_app(scheme: "MyApp")
upload_to_testflight(skip_waiting_for_build_processing: true)
end
That’s it. The upload returns as soon as the IPA is uploaded, the lane finishes, the runner is released. Total pipeline time drops to the build + upload steps only — typically 4–11 minutes instead of 9–37.
Every team that’s done this knows the problem with it: there’s now no notification when the build is actually ready — no message in Slack, no card in Teams, no Discord ping, no PagerDuty resolution, nothing. The team has to either:
- Check App Store Connect manually every five minutes to see if the build’s available for testers,
- Add a separate cron job that polls ASC and notifies (which is just moving the polling from one place to another),
- Or add a custom fastlane plugin that handles the wait elsewhere (someone has to maintain it).
This is where most teams give up and put skip_waiting_for_build_processing: false back. The team notification was load-bearing.
The actual fix: let Apple tell you
App Store Connect itself emits a webhook when a build’s processing state changes. The event is buildBetaStateUpdated for TestFlight processing (or buildStateUpdated for App Store distribution builds), and it fires automatically — no polling, no client code, no API credentials beyond the initial webhook setup. It arrives within seconds of the state actually transitioning on Apple’s side.
The fix is:
- Set
skip_waiting_for_build_processing: truein your fastlane lane (as above). - Point App Store Connect’s webhook URL at Juice Machine.
- Configure a destination in JM — Slack, Microsoft Teams, Discord, email, PagerDuty, Telegram, or a generic HTTP webhook into your own backend — and filter it to
buildBetaStateUpdatedevents. Done.
When ASC finishes processing the build, the webhook fires; JM enriches it with the human-readable build info (version, build number, processing state) and forwards a channel-appropriate message: a Block Kit message to Slack, an Adaptive Card to Teams, an embed to Discord, an incident to PagerDuty, an HTML email, or a versioned JSON payload to a generic webhook receiver. From the team’s perspective, the notification looks identical to what fastlane’s wait was producing. The difference is that no CI runner was sitting in a polling loop to make it happen.
The full lane, after:
lane :beta do
build_app(scheme: "MyApp")
upload_to_testflight(skip_waiting_for_build_processing: true)
# That's it. Team notification handled by ASC webhook → JM → your destination of choice.
end
Bonus: this covers states fastlane’s wait can’t
fastlane’s processing wait only watches for the build to transition into a final state — VALID, INVALID, or FAILED. It doesn’t tell you about anything that happens after that.
ASC keeps emitting webhook events for the lifetime of a build. So with the webhook setup, you also get notifications for:
- Export compliance prompts — when Apple flags the build for additional encryption review
- TestFlight availability state changes — when the build moves from “processing” to “ready to test” to “expired”
- Beta review status — when an external tester group needs Apple’s review and that review changes state
Most of these are events teams want visibility on, and fastlane’s processing wait can’t provide them by definition. So you’re not just removing the blocking wait; you’re getting more signal at the same time.
What it looks like end-to-end
Real timing for the same TestFlight pipeline, same build, same Apple processing duration:
Lane with wait |
Lane with webhook | |
|---|---|---|
xcodebuild archive |
6 min | 6 min |
| Upload IPA to ASC | 45s | 45s |
| Wait for ASC processing | 14 min | 0s |
| Lane exits | at 20:45 | at 6:45 |
| Team notification arrives | ~20:45 (after fastlane’s next 30s poll catches the state change) | ~20:45 (immediately, the moment ASC pushes the webhook) |
The notification arrives at roughly the same wall-clock moment in both cases — Apple’s processing dictates when, not the lane. If anything, the webhook gets there slightly earlier than fastlane would have: fastlane polls ASC every 30 seconds, so it can be up to a full poll interval behind the actual state transition, whereas ASC pushes the webhook immediately. The real difference is that with the webhook setup, the CI lane itself is long since done, the runner is released, and the next CI job in the queue is already running.
TL;DR
- fastlane’s
upload_to_testflightblocks your CI runner for 5–25 minutes polling App Store Connect for build processing. - The one-line fastlane fix is
skip_waiting_for_build_processing: true. - The reason most teams don’t do this is they lose the team notification (in Slack, Teams, Discord, email — wherever the team lives).
- Apple’s
buildBetaStateUpdatedwebhook delivers that notification anyway, without the blocked runner. - Juice Machine wires the webhook into Slack, Microsoft Teams, Discord, email, PagerDuty, Telegram, or any HTTP endpoint in a couple of clicks.
- Net: CI lanes finish in minutes instead of half-hours; team gets the same notification; everyone’s happier.