ZhgChg.Li

App Store Connect API Webhook|Automate CI/CD Workflows Seamlessly

Developers facing manual deployment delays can integrate App Store Connect API Webhooks to automate CI/CD pipelines, reducing errors and accelerating app releases efficiently.

App Store Connect API Webhook|Automate CI/CD Workflows Seamlessly

[CI/CD] Using App Store Connect API Webhook to Integrate Automated Workflows

Independent writing, free to read — please support these ads

 

Advertise here →

App Store Connect Webhook Use Case Analysis and Practical Integration Tutorial.

Photo by Volodymyr Hryshchenko

Photo by Volodymyr Hryshchenko

Introduction

Apple has continuously expanded the App Store Connect API in recent years, which is great news for developers. Previously, even certificate management relied on “hardcore” web sessions (with expiration and SMS verification), making CI/CD integration difficult. Features like store reviews could only depend on unstable RSS feeds.

In recent years, new features have been added almost every year, covering development, testing, and deployment processes, as well as native support for post-release reviews, finance, and data reports. Additionally, user management, groups, and TestFlight capabilities have been enhanced, allowing App Store Connect API to better improve the Apple developer experience.

Further reading: “App Store Connect API Now Supports Reading and Managing Customer Reviews

WWDC 2025 Automate your development process with the App Store Connect API

The 2025 WWDC also brings the highly anticipated major feature — Webhook Notifications:

  • Build Upload Status (The status of a build upload changes.)
    Receive related data when the build upload status changes.
    Complete / Failed / Processing

  • App Version Status (The status of an app version changes.)
    Receive related data when the app version status changes.
    Prepare for Submission / Ready for Review / Waiting for Review / Ready for Distribution / Rejected…

  • TestFlight Version Status (New TestFlight feedback is submitted by a tester.)
    Receive related data when a tester leaves feedback (crash reports/screenshots).

  • Apple-hosted Asset Pack Status Changes (The status of an Apple-hosted asset pack version changes.)
    Receive related data when specific changes occur to an Apple-hosted asset pack version.

App Store Connect API / Webhook notifications :

Webhooks allow a system to send real-time data to another system over the internet.

Webhook allows a system to instantly send data to another system over the internet.

Unlike traditional APIs, where one system must request data, a webhook allows you to push data to the receiving system immediately when an event happens.

Unlike traditional APIs, which require the receiver to actively send requests, Webhooks can instantly push data to the receiver as soon as the event happens.

Webhooks are event-driven, meaning they are triggered by a specific action or event and immediately send the relevant data to a predefined URL, also called the “webhook URL” or “callback URL.”

Webhooks are event-driven, meaning they instantly send relevant data to a pre-configured URL, called the “Webhook URL” or “Callback URL,” when a specific action or event occurs.

A notification webhook is an endpoint you create on your server.

Notification Webhook is an endpoint you create on your own server.

This webhook endpoint receives HTTP POST requests from App Store Connect.

This Webhook endpoint receives HTTP POST requests from App Store Connect.

The POST requests describe important events related to your app.

These POST requests describe important events related to your App.

Use the webhooks notifications endpoint to set up notifications for events occurring in your apps.

You can use Webhook notification endpoints to set up notifications for various events related to your App.

5 Use Cases

1. Trigger App Submission After Build Completion

Before:

In the past, when implementing App CI/CD packaging and submission, you had to wait for Apple to finish processing before continuing with the submission. Fastlane’s default method is to poll App Store Connect to check the uploaded build status, and only proceed with the submission lane once it is Complete.

The waiting time is about 20 minutes. This doesn’t matter much for self-hosted CI/CD, but if using cloud services, this 20-minute wait wastes a lot of resources. For example, GitHub Runner macOS costs US$0.062 per minute, so each waiting time for submission causes an unnecessary expense of US$1.24.

Ref: Build Completed Processing Notification Email with Gmail Filter + Google Apps Script

Ref: Build Completed Processing 通知信 搭配 Gmail Filter + Google Apps Script

In the era without Webhook active notifications, I previously used “Build Completed Processing notification emails combined with Gmail Filter + Google Apps Script to trigger” to achieve the effect, but the process was a bit hardcore.

After:

  • With Webhook, the packaging and uploading steps can finish immediately after the upload is complete.

  • A Webhook notification is sent after the App Store Connect Build Process completes. We proceed with the submission step upon receiving the notification.

  • Zero Waiting Cost

2. Align GitFlow Release Process with App Release Timing

Before:

The final step in GitFlow deployment requires merging the develop branch back into the master branch, which corresponds to the current live version.

Previously, tasks could only be executed manually or automatically on a fixed schedule, such as releasing the app every Monday afternoon and merging develop back to master on Monday. Manual execution is tedious, and with automatic execution, what if there’s a delay? Or if Monday is a holiday? The app might not actually be released, but develop has already been merged back to master.

In most cases, this is not very important. Only in extreme situations, such as needing to insert a hotfix during this time, might there be discrepancies. However, if you aim for a complete and stable CI/CD development process, this is a case worth exploring.

Another approach, similar to above, is using the “App is Ready for Sale notification email with Gmail Filter + Google Apps Script to trigger,” which is feasible.

After:

  • With Webhook, you can directly trigger CI/CD actions (Master to Develop) upon receiving app release notifications.

  • Ensure the App is truly live before merging back to Master

3. App Release Messages

After the app is released to users, another common internal workflow is sending release notifications to relevant teams, listing tasks included in the version, and completing related tasks.

Before:

Only manual, scheduled automation, or using email combined with Gmail Filter + Google Apps Script to trigger.

After:

  • With Webhook, after receiving an App release notification, you can connect to the Jira/Asana API to mark the corresponding version’s batch as Complete and send a release completion message for the included tasks to Slack.

4. Build Failed / Review Rejected Notifier

In sections 1 and 2, it was mentioned that previously there was a chance to trigger workflows via email notifications. However, in large teams with strict permission controls, iOS developers only have “developer” backend access and cannot release or manage the app, so they cannot receive any email notifications about app status changes; this includes rejection notices for uploaded builds or app review rejections.

Before:

In the past, we could only rely on kind-hearted people (a.k.a PMs) to forward emails to the engineers. If those kind-hearted people missed it, we might only find out the app was rejected and couldn’t go live right before the launch!

After:

  • This case is relatively simple: after receiving the Webhook notification, it forwards the message to Slack.

5. Testflight Feedback Notifier

Similar to 4, but connects to TestFlight Feedback Webhook notifications.

Before:

In the past, developers could only check tester feedback or crash issues by logging into the App Store Connect TestFlight backend themselves, which was very easy to overlook (there were even cases where feedback reported a year ago was only seen a year later).

After:

  • Forward Testflight Feedback Webhook notifications to Slack after receiving them.

— — —

歡迎其他創意使用案例。接下來,我們將介紹如何整合與使用它。

App Store Connect API Webhook Setup

Permission Requirements: Admin or Account Holder privileges are required to configure.

Setting Up App Store Connect API Webhook Notifications

  1. Go to App Store Connect Dashboard

  2. Go to “Users and Access” -> “Integrations”

  3. Click “Webhooks” under “Additional”

  4. Click the “Create Webhook” button

  • Name: Enter the Webhook name

  • Payload URL: Enter the URL of your Webhook notification receiver service

  • Secret String: Webhook request verification key (you can generate a random string for use)

  • App: Select the App to receive Webhook notifications in advance

  • Trigger Events:

TestFlight Feedback
Receive related data when testers leave feedback.
[] Crash Feedback
[] Screenshot Feedback

[] TestFlight Version Status
Receive related data when the TestFlight version status changes. Learn more

[] App Version Status
Receive related data when the App version status changes. Learn more

[] Build Upload Status
Receive related data when the build upload status changes. Learn more

Background Assets
Receive related data when specific changes occur in Apple-hosted asset pack versions. Learn more

[] Update App Store Release Version
[] Update External TestFlight Release Version
[] Create Internal TestFlight Release Version
[] Update Asset Pack Version

Select according to your needs, or choose all notifications and decide whether to process them afterward.

Finally, click “Add” to create the Webhook.

Testing App Store Connect API Webhook Notifications

Go to the Webhook page.

Click the “Test” button at the top right to receive a test notification.

The test notification content is as follows:

Headers:
{
  "content-type": "application/json",
  "x-apple-jingle-correlation-key": "PNSCHDQW3MY2AX6VSRFHYYNUL4",
  "x-apple-request-uuid": "7b64238e-16db-31a0-5fd5-944a7c61b45f",
  "x-apple-signature": "hmacsha256=cf50020f0bbd3c5274860594f616f1806965c1f9fb765d8d278f512dff5b4c0e",
}

Body:
{
  "data" : {
    "type" : "webhookPingCreated",
    "id" : "65726e27-cb79-47f2-a3e4-c8ced9f356e8",
    "version" : 1,
    "attributes" : {
      "timestamp" : "2025-12-26T15:47:38.472168681Z"
    }
  }
}

App Store Connect API Webhook Notification Sending Records

The “Recent Deliveries” section at the bottom of the Webhook page shows the most recently sent Webhook events.

Verify App Store Connect API Webhook Notifications

When creating a Webhook, we input a “Secret” string. It is recommended to verify requests to prevent the Webhook URL from leaking, which could allow malicious actors to forge and send Webhook events to your service.

Verification Method:

Perform HMAC-SHA256 on the Request Body using your set secret key and output a HEX string. Compare this string with the value after hmacsha256= in the x-apple-signature field of the Request Headers.

Implementation — Nodejs:

import crypto from 'crypto';

function verifyAppleWebhook(rawBody, appleSignature, secret) {
  const hex = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');

  return `hmacsha256=${hex}` === appleSignature;
}

Implementation — Cloudflare Worker:

function bufferToHex(buffer) {
  return [...new Uint8Array(buffer)]
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

async function hmacSha256Hex(secret, message) {
  const enc = new TextEncoder();

  const key = await crypto.subtle.importKey(
    'raw',
    enc.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  );

  const signature = await crypto.subtle.sign(
    'HMAC',
    key,
    enc.encode(message)
  );

  return bufferToHex(signature);
}

async function verifyAppleWebhook(request, secret) {
  const appleSignature = request.headers.get('X-Apple-Signature');

  const rawBody = await request.clone().text();
  const calculated = await hmacSha256Hex(secret, rawBody);

  return "hmacsha256="+calculated === appleSignature;
}
  • Cloudflare Worker does not have the crypto module, so you need to use the Web Crypto API (crypto.subtle)

Implementation — Google Apps Script Web App ❌

Due to technical limitations, Google Apps Script Web App doGet(e)/doPost(e) cannot access Request Headers, so this method cannot be used to verify the request source.

You can only add some key parameters in the URL query for simple validation and protection.

App Store Connect API Webhook Notification Payload

Independent writing, free to read — please support these ads

 

Advertise here →

Here are some collected event Payloads received during the App upload and review process, provided for your direct reference in automation development.

Webhooks only send event and status names, without other detailed information such as version number or rejection reasons; we need to call the App Store Connect API ourselves using the Relationships Link in the Event Payload to get the full details.

Build Version Upload — Process Complete

{
  "data" : {
    "type" : "buildUploadStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "oldState" : "PROCESSING",
      "newState" : "COMPLETE"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "buildUploads",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

Build Version Upload — Process Failed

{
  "data": {
    "type": "buildUploadStateUpdated",
    "id": "xxx-xx-xx-xx-xxx",
    "version": 1,
    "attributes": {
      "oldState": "PROCESSING",
      "newState": "FAILED"
    },
    "relationships": {
      "instance": {
        "data": {
          "type": "buildUploads",
          "id": "xxx-xx-xx-xx-xxx"
        },
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

Mostly due to binary rejections, such as using the microphone without declaring it.

App Version Status — Prepare For Submission

{
  "data" : {
    "type" : "appStoreVersionAppVersionStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "newValue" : "PREPARE_FOR_SUBMISSION",
      "oldValue" : "DEVELOPER_REJECTED",
      "timestamp" : "2025-12-18T05:01:47.118Z"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "appStoreVersions",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

When a new version number is created and ready for submission, this stage allows you to fill in version information, update details, and select the Build to submit for review.

App Version Status — Ready For Review

{
  "data" : {
    "type" : "appStoreVersionAppVersionStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "newValue" : "READY_FOR_REVIEW",
      "oldValue" : "PREPARE_FOR_SUBMISSION",
      "timestamp" : "2025-12-18T03:41:12.516Z"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "appStoreVersions",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

When the submission data is confirmed and ready for review.

App Version Status — Waiting For Review (Submitted, Awaiting Review)

{
  "data" : {
    "type" : "appStoreVersionAppVersionStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "newValue" : "WAITING_FOR_REVIEW",
      "oldValue" : "READY_FOR_REVIEW",
      "timestamp" : "2025-12-18T03:41:21.179Z"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "appStoreVersions",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

The app has been submitted for review and is awaiting approval.

App Version Status — Developer Rejected

{
  "data" : {
    "type" : "appStoreVersionAppVersionStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "newValue" : "DEVELOPER_REJECTED",
      "oldValue" : "WAITING_FOR_REVIEW",
      "timestamp" : "2025-12-18T03:50:30.552Z"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "appStoreVersions",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

The developer withdraws the version that is currently under review.

App Version Status — In Review (Official Review in Progress)

{
  "data" : {
    "type" : "appStoreVersionAppVersionStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "newValue" : "IN_REVIEW",
      "oldValue" : "WAITING_FOR_REVIEW",
      "timestamp" : "2025-12-18T22:05:50.038Z"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "appStoreVersions",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

App Version Status — Pending Developer Release (Approved and Waiting for Release)

{
  "data" : {
    "type" : "appStoreVersionAppVersionStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "newValue" : "PENDING_DEVELOPER_RELEASE",
      "oldValue" : "IN_REVIEW",
      "timestamp" : "2025-12-18T22:34:18.785Z"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "appStoreVersions",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

The time of the Pending Developer Release event minus the time of the Waiting For Review event equals the waiting time between app submission and being ready for release.

App Version Status — Ready for Distribution (App Ready to Publish) a.k.a Ready For Sale

{
  "data" : {
    "type" : "appStoreVersionAppVersionStateUpdated",
    "id" : "xxx-xx-xx-xx-xxx",
    "version" : 1,
    "attributes" : {
      "newValue" : "READY_FOR_DISTRIBUTION",
      "oldValue" : "PENDING_DEVELOPER_RELEASE",
      "timestamp" : "2025-12-23T06:03:50.925Z"
    },
    "relationships" : {
      "instance" : {
        "data" : {
          "type" : "appStoreVersions",
          "id" : "xxx-xx-xx-xx-xxx"
        },
        "links" : {
          "self" : "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"
        }
      }
    }
  }
}

App is ready for release (almost equivalent to Ready For Sale, but there is no Ready For Sale event).

Your app has been accepted and is ready for distribution.

To distribute your app, your agreements must be active. The Account Holder can accept the latest agreements in the Business section.

App Store Connect API Webhook Workflow Integration

Method 1 — Using Fastlane to Connect with App Store Connect API

The fastest way here is to trigger it directly with a CI/CD service, then use Fastlane’s built-in Spaceship to connect to the App Store Connect API.

If you already use the App Store Connect API with Fastlane to manage Match certificates and submissions, this method can be used directly without hassle; if not, first refer to the official documentation to generate an API Key and securely store it in your CI/CD service secrets.

  • App Store Connect
  1. When the App status changes

  2. Trigger Webhook

  • Webhook Endpoint
    Can be a self-hosted service/API or simply use FAAS services (Cloudflare Worker / AWS Lambda / Cloud Functions / Google Apps Script)
  1. Verify Webhook (optional)

  2. Handling Webhook events and forwarding event requests to CI/CD services for execution
    e.g. Triggering GitHub Actions via GitHub API..

  • CI/CD Service
    GitHub Actions / Bitbucket Pipeline / Gitlab Runner…
  1. Trigger Action

  2. Run Fastlane Script, Reuse Fastlane Spaceship Authentication

  • App Store Connect
  1. Using App Store Connect API to Obtain Complete Information
  • CI/CD Service
  1. Subsequent steps, such as sending notifications or triggering another action

Fastlane Example:

  # Usage:
  #   bundle exec fastlane appStoreConnectWebhookHandler \
  #     data:'{"data":{"type":"buildUploadStateUpdated","id":"xxx-xxx-xxx-xx-xxx","version":1,"attributes":{"oldState":"PROCESSING","newState":"COMPLETE"},"relationships":{"instance":{"data":{"type":"buildUploads","id":"xxx-xxx-xxx-xx-xxx"},"links":{"self":"https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xxx-xxx-xx-xxx"}}}}}'
  # Notes:
  # - `data:` must be a JSON string.
  # - This lane is intended for local debugging (it prints the GET response).
  desc "[Automation] Handle App Store Connect webhook payload and fetch related instance via ASC API"
  lane :appStoreConnectWebhookHandler do \\|options\\|
    begin
      data = options[:data]
      UI.user_error!("Missing data") if data.empty?
      data = JSON.parse(data)
      url = data.dig("data", "relationships", "instance", "links", "self").to_s.strip
      UI.user_error!("Missing instance self url in JSON") if url.empty?

      api_key = app_store_connect_api_key(
        key_id: "xxxx",
        issuer_id: "xxxx-xxxx-xxxx-xxxx-165aa6465141",
        key_filepath: "./AuthKey_xxxx.p8",
        duration: 1200, # optional (maximum 1200)
        in_house: false # optional but may be required if using match/sigh
      )

      loadAppStoreConnectAPIKey
      #
      uri = URI.parse(url)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_PEER
      store = OpenSSL::X509::Store.new
      store.set_default_paths
      http.cert_store = store

      request = Net::HTTP::Get.new(uri.request_uri)
      token = Spaceship::ConnectAPI.token
      UI.user_error!("App Store Connect API token is not available. Make sure app_store_connect_api_key is configured correctly.") if token.nil?
      request['Authorization'] = "Bearer #{token.text}"

      request['Content-Type'] = 'application/json'
      request['Accept'] = 'application/json'

      response = http.request(request)
      UI.message("📡 GET #{url} Response: [#{response.code}] #{response.message}")
      UI.message(response.body)
      #

      response
      ## handle response...do next actions...
      
    rescue => e
        UI.error("❌ Failed handle App Store Connect API Webhook: #{e}")
    end

  end

Method 2 — Handle It Yourself at the Webhook Endpoint

The second method is to handle everything directly on the Webhook Endpoint service, but the drawback is that you need to put the App Store Connect API Key on the service and handle Token verification yourself.

Ruby Example:

require 'jwt'
require 'net/http'
require 'time'

keyFile = File.read('./AuthKey_XXXX.p8') # replace with your .p8 private key file (download from App Store Connect)
privateKey = OpenSSL::PKey::EC.new(keyFile)

payload = {
            iss: 'YOUR_ISSUE_ID', # replace with your issuer ID (found in App Store Connect user access -> keys -> App Store Connect API page)
            iat: Time.now.to_i,
            exp: Time.now.to_i + 60*20,
            aud: 'appstoreconnect-v1'
          }

token = JWT.encode payload, privateKey, 'ES256', header_fields={kid:"YOUR_KEY_ID", typ:"JWT"} # replace with your key ID (found in App Store Connect user access -> keys -> App Store Connect API page)
puts token

decoded_token = JWT.decode token, privateKey, true, { algorithm: 'ES256' }
puts decoded_token

# Replace with relationships link from Webhook Payload
uri = URI("https://api.appstoreconnect.apple.com/v1/apps/APPID/customerReviews") # replace APPID with your app ID in App Store Connect -> your app -> app information -> Apple ID
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true

request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{token}";

response = https.request(request)
puts response.read_body

For generating App Store Connect API Keys, creating Tokens, and using the API, please refer to “App Store Connect API now supports reading and managing Customer Reviews”.

App Store Connect API Response

Here are some example responses received after getting the Relationships Link to obtain complete information following a Webhook Event.

Build Version Upload — Process Complete

https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx

{
  "data": {
    "type": "buildUploads",
    "id": "xx-xx-xx-xxx-xx",
    "attributes": {
      "cfBundleShortVersionString": "1.101.0",
      "cfBundleVersion": "1",
      "createdDate": "2025-12-25T08:26:43-08:00",
      "state": {
        "errors": [],
        "warnings": [],
        "infos": [],
        "state": "COMPLETE"
      },
      "platform": "IOS",
      "uploadedDate": "2025-12-25T08:28:35-08:00"
    },
    "relationships": {
      "buildUploadFiles": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx/relationships/buildUploadFiles",
          "related": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx/buildUploadFiles"
        }
      }
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx"
    }
  },
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx"
  }
}

Build Version Upload — Process Failed

https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx

{
  "data": {
    "type": "buildUploads",
    "id": "xx-xx-xx-xx-xxx",
    "attributes": {
      "cfBundleShortVersionString": "1.101.0",
      "cfBundleVersion": "3",
      "createdDate": "2025-12-12T09:03:32-08:00",
      "state": {
        "errors": [
          {
            "code": "90683",
            "description": "Missing purpose string in Info.plist. Your app’s code references one or more APIs that access sensitive user data, or the app has one or more entitlements that permit such access. The Info.plist file for the “My.app” bundle should contain a NSMicrophoneUsageDescription key with a user-facing purpose string explaining clearly and completely why your app needs the data. If you’re using external libraries or SDKs, they may reference APIs that require a purpose string. While your app might not use these APIs, a purpose string is still required. For details, visit: https://developer.apple.com/documentation/uikit/protecting_the_user_s_privacy/requesting_access_to_protected_resources."
          }
        ],
        "warnings": [],
        "infos": [],
        "state": "FAILED"
      },
      "platform": "IOS",
      "uploadedDate": "2025-12-12T09:05:26-08:00"
    },
    "relationships": {
      "buildUploadFiles": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx/relationships/buildUploadFiles",
          "related": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx/buildUploadFiles"
        }
      }
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx"
    }
  },
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx"
  }
}

Taking ITMS-90683 as an example.

App Version Status

https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx

{
  "data": {
    "type": "appStoreVersions",
    "id": "xxx-xxx-xxx-xxx",
    "attributes": {
      "platform": "IOS",
      "versionString": "1.101.0",
      "appStoreState": "READY_FOR_SALE",
      "appVersionState": "READY_FOR_DISTRIBUTION",
      "copyright": "© 2025 ZhgChgLi.",
      "reviewType": "APP_STORE",
      "releaseType": "MANUAL",
      "earliestReleaseDate": null,
      "usesIdfa": null,
      "downloadable": true,
      "createdDate": "2025-12-15T19:12:55-08:00"
    },
    "relationships": {
      "ageRatingDeclaration": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/ageRatingDeclaration",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/ageRatingDeclaration"
        }
      },
      "appStoreVersionLocalizations": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionLocalizations",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionLocalizations"
        }
      },
      "build": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/build",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/build"
        }
      },
      "appStoreVersionPhasedRelease": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionPhasedRelease",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionPhasedRelease"
        }
      },
      "gameCenterAppVersion": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/gameCenterAppVersion",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/gameCenterAppVersion"
        }
      },
      "routingAppCoverage": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/routingAppCoverage",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/routingAppCoverage"
        }
      },
      "appStoreReviewDetail": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreReviewDetail",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreReviewDetail"
        }
      },
      "appStoreVersionSubmission": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionSubmission",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionSubmission"
        }
      },
      "appClipDefaultExperience": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appClipDefaultExperience",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appClipDefaultExperience"
        }
      },
      "appStoreVersionExperiments": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionExperiments",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionExperiments"
        }
      },
      "appStoreVersionExperimentsV2": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionExperimentsV2",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionExperimentsV2"
        }
      },
      "customerReviews": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/customerReviews",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/customerReviews"
        }
      },
      "alternativeDistributionPackage": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/alternativeDistributionPackage",
          "related": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/alternativeDistributionPackage"
        }
      }
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx"
    }
  },
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx"
  }
}

As mentioned earlier, detailed app information, version number, and error reasons can only be obtained by calling the API.

Done

Independent writing, free to read — please support these ads

 

Advertise here →

By now, we can use the App Store Connect API Webhook to better enhance App CI/CD and automation workflows, improving team development efficiency.

Further Reading

Improve this page
Edit on GitHub
Also published on Medium
Read the original
Share this essay
Copy link · share to socials
ZhgChgLi
Author

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

Comments