App Store Connect の審査状況を Slack に流す「asc-slack-notifier」

·2 分で読めます

App Store Connect の webhook を受けて、審査状態やビルドの変化を Slack に通知するサーバー asc-slack-notifier を作りました。

モチベーション

App Store Connect の審査通知はアカウント個人へのメールで届くため、これまではメールを見た自分がチームの Slack で「届いてましたね」とやりとりを始めていました。

それなら通知そのものを Slack に流し、通知への thread reply をコミュニケーションの起点にすればよいと思いつき、webhook を Slack に中継する受け口を書いたのがこのツールです。

作ってみると webhook の payload はかなり素っ気なく、審査状態の変化イベントに入っているのは リソースの UUID と新旧の状態だけ で、どのアプリのどのバージョンが動いたのかは payload からは分かりません。

複数アプリを 1 つのチャンネルに流すとそれでは役に立たないため、App Store Connect API キーを渡しておくと通知前にリソースを引いて、アプリ名・バージョン・ビルド番号と「App Store Connect で開く」ボタンを付ける補完機能を足しました。

使い方

webhook を受けると、状態ごとの絵文字付きで Block Kit メッセージを組み立てて Slack に投稿します (READY_FOR_REVIEW 📝、PENDING_APPLE_RELEASE ⏳、READY_FOR_DISTRIBUTION ✅、REJECTED ❌ など)。

Apple がまだドキュメント化していないイベントタイプも汎用の key/value 表示で必ず流すため、通知が黙って捨てられることはありません。

Slack への投稿先は Incoming Webhook URL か、bot トークン + チャンネル (chat.postMessage) のどちらかを選べます。

App Store Connect API キー (DeveloperApp Manager ロールの読み取りだけで十分) を設定すると、審査状態とビルド系の通知に App / Version / Build のフィールドが付き、ボタンからアプリの配信ページや TestFlight ページに直接飛べます。

補完はオプションなため、API が落ちていても通知自体は素の内容で届きます。

導入方法

いちばん手軽なのはリポジトリを fork して GitHub Actions に任せる方法で、リポジトリ変数 DEPLOY_TARGETcloudrunlambda にしてシークレットを設定すれば、master/main への push で自分のインスタンスがデプロイされます。

手順は docs/DEPLOYMENT.md にまとめました。

手動なら Cloud Run はこれだけです。

PROJECT_ID=your-project
REGION=asia-northeast1
IMAGE="$REGION-docker.pkg.dev/$PROJECT_ID/apps/asc-slack-notifier:latest"

gcloud builds submit --tag "$IMAGE"
gcloud run deploy asc-slack-notifier \
  --image "$IMAGE" \
  --region "$REGION" \
  --allow-unauthenticated \
  --set-secrets "ASC_WEBHOOK_SECRET=asc-webhook-secret:latest,SLACK_WEBHOOK_URL=slack-webhook-url:latest"

デプロイできたら App Store Connect API で webhook を登録します (eventTypes は本文で扱う 3 種類だけを載せた例で、Beta feedback 系を含む全量は README にあります。$TOKEN は API キーから作った JWT、$APP_ID は対象アプリの App Store Connect ID)。

curl -sS -X POST 'https://api.appstoreconnect.apple.com/v1/webhooks' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "data": {
      "type": "webhooks",
      "attributes": {
        "name": "Slack notifier",
        "url": "https://your-service.example.com/webhook",
        "secret": "your-webhook-secret",
        "enabled": true,
        "eventTypes": [
          "APP_STORE_VERSION_APP_VERSION_STATE_UPDATED",
          "BUILD_UPLOAD_STATE_UPDATED",
          "BUILD_BETA_DETAIL_EXTERNAL_BUILD_STATE_UPDATED"
        ]
      },
      "relationships": {
        "app": { "data": { "type": "apps", "id": "'"$APP_ID"'" } }
      }
    }
  }'

webhook の secret は Apple が発行してくれるものではなく 自分で決める値 なため、openssl rand -hex 32 などで作った同じ文字列を、webhook 登録時の attributes.secret とサーバー側の ASC_WEBHOOK_SECRET の両方に渡します。

ここが食い違うと配信はすべて 401 で弾かれます。

API キーの秘密鍵は .p8 ファイルのパスを ASC_API_PRIVATE_KEY_PATH に渡すか、PEM の中身そのままか base64 エンコードした文字列を ASC_API_PRIVATE_KEY に渡すかを選べるため、fastlane の key_content と同じくシークレットに文字列しか入れられないプラットフォームでも困りません。

Under the hood

Go の 1 バイナリで、環境変数ひとつで Cloud Run のような普通の HTTP サーバーとしても、AWS Lambda + API Gateway の背後でも動きます。

受信時は x-apple-signature ヘッダーの HMAC-SHA256 署名を生のリクエストボディに対して検証し、比較は constant time で行います。

Slack に届けられなかったときは 502 を返すため、App Store Connect 側の配信履歴に失敗として残り、そこから再送できます。

フィードバックのお願い

自分のリリース番の道具として作りましたが、iOS アプリをチームで運用しているところならどこでも使えるはずです。

MIT ライセンスで公開しています。

不具合や機能要望は GitHub の Issue や Pull Request でご連絡ください。