Sign in with Appleの通知は3種類ではなく4種類届きます
Sign in with Appleのサーバー間通知に関するAppleの開発者向けアナウンスでは、エンドポイントが受け取るものとして3つが挙げられています。メール転送設定の変更、アプリ内でのアカウント削除、そしてApple Accountの完全削除です。3 一方、APIのドキュメントは4つの異なるイベントタイプを定義しています。1
アナウンスだけを頼りに実装すると、分岐は3本になり、4つ目のイベントは黙って捨てられます。 アナウンスがemail-enabledとemail-disabledを「転送設定」というひとつの項目にまとめているからです。実際には、この2つは別々のtype値を持つ別々の通知として届きます。default節を書かずにtypeで分岐するコードは、書き手が忘れたほうを黙って無視することになります。
さらに、4つのうち2つは名前から受ける印象以上の意味を持ち、そのうちひとつはアプリの認証状態そのものを変えてしまいます。
要点
Sign in with Appleが配信するサーバー間通知は4種類です。email-enabled、email-disabled、consent-revoked、account-deleted。1 ペイロードはAppleの秘密鍵で署名されたJWSで、payloadキーを持つJSONオブジェクトに包まれた形で届きます。処理に入る前に、ヘッダーのalgパラメーターで指定されたアルゴリズムを使って署名を検証しなければなりません。1 consent-revokedはユーザーの資格情報を無効化するため、設定変更ではなく認証イベントとして扱う必要があります。Apple Accountが削除されてもネイティブアプリにはクライアント側のコールバックが届かないため、サーバー通知だけが手がかりとなります。1 なお、再送や配信保証についてAppleは何も文書化していません。
4つのイベントタイプ
各通知はeventsクレームの中にtype値を持ちます。1
type |
何が起きたか |
|---|---|
email-enabled |
ユーザーが「メールを非公開」を使って、個人アドレスへのメール転送を有効にした |
email-disabled |
ユーザーがメール転送を無効にした |
consent-revoked |
ユーザーがアプリへの同意を取り消し、資格情報が無効になった |
account-deleted |
ユーザーがApple Accountの完全削除を要求した |
アナウンスがひとつにまとめてしまっているのが、このメール関連の2つです。しかし両者は独立した意味を持ちます。email-disabledは、リレーアドレス宛に送ったメールがユーザーに届かなくなったことを意味し、email-enabledは再び届くようになったことを意味します。「設定が変わった」というひとつのイベントとして扱えば、現在の状態をわざわざ問い合わせに行く羽目になります。通知がすでに教えてくれていた情報なのに、です。
見た目どおりではない2つのイベント
consent-revokedは認証イベントです。 Appleの説明はこうです。ユーザーが「自分のApple Accountをアプリが利用することへの同意を取り消し、その資格情報が無効になる」。1 非推奨になるのでも、期限切れが近づくのでもありません。無効です。
これをメール系イベントと並べてログに記録し、設定テーブルの行を更新するだけのアプリは、もはや認証を通らない資格情報を裏に抱えたまま、サインイン済みの画面を出し続けることになります。ユーザーには次のトークン更新が失敗するまでアカウントが見えたままで、その後に待っているのはもっとひどい状態です。正しい扱いは、セッションを終了させ、再認証へ誘導することです。取り消されたOAuthの認可に対して使うのと同じコードパスで構いません。
account-deletedは、唯一の通知になり得ます。 Appleによれば、ユーザーがApple Accountを完全に削除すると、Sign in with Appleはすべてのユーザートークンを無効化し、関連するすべてのアプリでメール転送を無効にします。そしてネイティブアプリの場合、システムはクライアント側のコールバックを送りません。1
この一文こそ、そもそもエンドポイントを運用すべき最大の理由です。サーバー間エンドポイントを持たない、iOSアプリだけのチームには、アカウントが消えたことを知る手段が存在しません。レコードは残り続け、リレーアドレスは機能しなくなり、削除義務があっても果たされないままです。削除すべきものがあること自体、誰も教えてくれないからです。
エンドポイントの登録
設定はCertificates, Identifiers & Profilesで行います。Identifiersを選び、対象のApp IDを開き、Sign in with Appleサービスを有効にしてConfigureをクリックし、エンドポイントのURLを指定します。2
設計に入る前に、制約を読んでおく価値があります。2
- Sign in with Appleのアプリグループとキーの組み合わせごとに1つのURL。 アプリごとに1つではありません。
- 登録できるのはプライマリApp IDのみ。
- URLはスキーム、ホスト、パスを備えた絶対URIである必要があります。
https://example.com/path/to/endpoint - 通知を受け取るにはTLS 1.2以降が必須です。
AppleのAPIのドキュメントには、同じURLを複数のデベロッパチームやアプリで使ってもよいという記述もあります。1 グループごとに1つのURLというルールと合わせて読むと、単一のサービスがすべてを受け取り、各グループがそこへのポインタを個別に登録する、という理解が妥当でしょう。この相互関係をAppleは明示していないため、共有エンドポイントは「動く」ものではあっても「公認」ではないと捉えてください。そして依存する前に、どのアプリに関する通知なのかをハンドラー側で判別できるようにしておきましょう。
TLS 1.2という下限は、より大きな流れとつながっています。OS 27では管理トラフィックに対するTLS要件の適用が厳格化され、同じ1.2という最低ラインに加えて、ATS準拠の暗号スイートと証明書が求められるようになりました。今日Appleの要件を満たしているエンドポイントが、自動的にATS準拠になるわけではありません。そして流れの向きは、緩むほうではなく厳しくなるほうです。
ペイロードを読む
配信はHTTP POSTで届き、ボディはJSONオブジェクト、その中に署名済みトークンが入っています。1
{
"payload": "<SERVER_TO_SERVER_NOTIFICATION_JWS>"
}
JWSは生のままではなく、包まれています。 まずJSONをパースし、payloadを取り出し、それから検証します。リクエストボディ全体をJWSの検証器に渡す実装は、最初の通知でつまずきます。しかも失敗は「包み方の間違い」ではなく「不正な形式のトークン」として現れるので、見当違いの場所を探すことになります。
検証は解釈より先です。ペイロードはJSON Web Signature形式でAppleの秘密鍵によって暗号署名されており、Appleの指示は、JWSを調べてヘッダーのalgパラメーターで指定されたアルゴリズムを使い署名を検証すること、というものです。1 署名が通ってはじめて、eventsクレームを読み、typeで分岐します。
一般的なJWSの実務から持ち込むべき習慣が2つあります。呼び出し側に検証を弱められるようなalg値は決して信用しないこと。そして、形式さえ整ったApple署名トークンなら何でも受け入れるのではなく、トークンの発行者と対象者が想定どおりかを確認することです。
デコード後の形
検証を通したconsent-revoked通知は、デコードするとこうなります。1
{
"iss": "https://appleid.apple.com",
"aud": "com.mytest.app",
"iat": 1508184845,
"jti": "abede...67890",
"events": {
"type": "consent-revoked",
"sub": "820417.faa325acbc78e1be1668ba852d492d8a.0219",
"event_time": 1508184845
}
}
account-deletedは同じフィールドをtypeだけ変えて運びます。メール系イベントはさらにemailとis_private_emailの2つが加わります。
この形には、不意打ちで出会うと時間を奪われる細部が3つあります。
eventsは配列ではなくオブジェクトです。 名前は複数形なのに、値は単一のイベントです。形ではなく名前を見て書かれたコードは、辞書をイテレートしてキーを受け取ることになります。
is_private_emailは文字列です。 Appleの例では"true"と引用符付きで示されており、JSONのboolean値trueではありません。これをBoolにマッピングする厳格なデコーダーは失敗します。空でない文字列をすべて真とみなす寛容なデコーダーは、間違った理由で正しい答えを出したうえで、"false"を取り違えます。
subは安定したユーザー識別子です。 サインイン時に受け取ったのと同じ値であり、この通知がどのアカウントに関するものかを突き止める手がかりになります。audはクライアント識別子で、共有エンドポイントが複数アプリの通知を振り分けられるのはこれのおかげです。
Apple自身のサンプルについて一点。メール系の2つのペイロードは、"is_private_email": "true"と"event_time"の間のカンマが抜けています。どちらのブロックもJSONのパーサーにそのまま貼れば、ドキュメントとして拒否されます。構造は正しいのに、句読点が正しくありません。テスト用のフィクスチャに貼り付けた読者は、自分のせいではない構文エラーに10分を溶かすことになります。
Appleの用語も揺れています。本文ではJSON Web Signature形式のペイロードと説明する一方、ラッパーの例では値の名前がSERVER_TO_SERVER_NOTIFICATION_JWTになっています。1 同じものです。署名されたJWTとは、JSONペイロードを持つJWSのことですから。ドキュメントを検索して両方の語に出会ったとき、知っておくと役に立ちます。
ハンドラーの全体像
正しいハンドラーの形は、ここまでの制約から自然に導かれます。PythonとPyJWTでの例です。
import json
import jwt
from jwt import PyJWKClient
# Apple publishes its signing keys as a JWKS. Cache the client;
# it fetches and caches keys rather than hitting Apple per request.
JWKS = PyJWKClient("https://appleid.apple.com/auth/keys")
CLIENT_ID = "com.mytest.app" # your aud value
def handle_notification(request_body: bytes):
# 1. The JWS is wrapped in JSON under "payload", not the raw body.
wrapper = json.loads(request_body)
token = wrapper["payload"]
# 2. Resolve the signing key by the token's kid, then verify.
# Pin the algorithm. Never read alg from the token to decide.
signing_key = JWKS.get_signing_key_from_jwt(token)
claims = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=CLIENT_ID,
issuer="https://appleid.apple.com",
)
# 3. Only now is anything trustworthy.
event = claims["events"] # an object, not a list
apple_user_id = event["sub"] # stable identifier from sign-in
match event["type"]:
case "consent-revoked" | "account-deleted":
end_all_sessions(apple_user_id)
mark_account_unlinked(apple_user_id)
case "email-disabled":
set_email_forwarding(apple_user_id, enabled=False)
case "email-enabled":
set_email_forwarding(apple_user_id, enabled=True)
case other:
log_unknown_event(other) # do not fail silently
このうち何行かが、構造を支えています。
アルゴリズムを固定すること。 Appleが現在公開しているJWKSにはRSA鍵が3つあり、いずれもRS256、use: sig、そして異なるkid値を持ちます。トークンからalgを読むのではなくalgorithms=["RS256"]を渡すことで、攻撃者がalg: noneを名乗るトークンを差し込む古典的なダウングレード経路を塞げます。
先頭の鍵を取るのではなく、kidで引くこと。 3つの鍵が同時に有効ですが、これは外から見た鍵ローテーションの姿にほかなりません。keys[0]をつかむハンドラーはAppleがローテーションするまでは動き、その後は一部のトークンで失敗します。デバッグには最悪の部類です。
audとissを検証すること。 これを怠ると、正しく署名されたAppleのトークンなら何でも受け入れてしまいます。別のアプリ向けに発行されたものも含めてです。
default分岐を残すこと。 そうしないと、5つ目のイベントタイプが消えてしまいます。まさにそれが、Appleの3項目のアナウンスから書き起こされた実装が、いまemail-enabledやemail-disabledを取りこぼしている理由です。
Appleが文書化していないこと
APIのドキュメントにも、アカウント関連のヘルプページにも、配信保証についての記述はありません。再送、再配信、受領確認、ステータスコード、タイムアウト、冪等性といった語で両方のページを検索しても、何も出てきません。
したがって、執筆時点でAppleのドキュメントが答えていないのは次の点です。
- 配信に失敗した場合に再送されるのか、されるとして何回か
- 再送されるとして、どの時間枠で行われるのか
- 成功を伝えるためにエンドポイントはどのステータスコードを返すべきか
- 同じ通知が2回届くことがあるのか
この空白は設計に影響します。そしてここからは、Appleが述べていることの報告ではなく、その先の推論です。配信のふるまいが規定されていないエンドポイントは、権威あるイベントストリームとして扱えません。守りの構えとしては、各通知を「何かが変わった」という手がかりとして受け取り、イベントを鵜呑みに適用するのではなく自前のレコードと突き合わせることです。重複が起こらないとは言い切れない以上、ハンドラーは冪等にしましょう。そして、すべての通知を受け取れた前提に正しさが依存するフローは作らないことです。受け取れたかどうかを確認する手立てがないのですから。
エンドポイントが1時間停止したとして、その1時間分のアカウント削除を失ったのか、それともどこかに滞留しているのか、ドキュメントからは判断できません。失われたものとして設計してください。
テストする手段も文書化されていない
サンドボックス、シミュレーション、トリガーといった語で両ページを検索しても、何も出てきません。任意のタイミングで通知を発火させる仕組みを、Appleは文書化していないのです。
ここに厄介な循環が生まれます。最も重要なイベントであるconsent-revokedとaccount-deletedは、ユーザーがアプリへのアクセスを取り消すか、Apple Accountを削除することでしか発生しません。本物のaccount-deletedでハンドラーを検証するということは、誰かがApple Accountを削除するということです。二度は走らせられないテストです。
現実的な代替は、問題を分けることです。上に示した形からデコード後のペイロードを自分で組み立て、分岐、冪等性、突き合わせのロジックをそれに対してユニットテストします。そのうえで、伝送と署名の経路は、実際に自分で発生させられる本物の通知で別途テストします。テスト用のApple Accountで同意を取り消す操作は、アカウント削除と違って元に戻せますし、ラッパーのパース、kidの解決、署名の検証までを通しで動かせます。
いずれにせよ、登録する前にエンドポイントが到達可能で、速やかに応答することを確認してください。登録したまま一度も検証していないエンドポイントこそ、公開以来すべての通知が期限切れの証明書の裏にあるURLへ飛んでいた、と数か月後にチームが気づく筋書きの入り口です。
すでに発効している要件
そもそもこれが開発者向けニュースに現れた理由はこうです。2026年1月1日以降、大韓民国に拠点を置く開発者は、Sign in with Appleでウェブサイトとアプリを関連付けるために、新しいServices IDを登録する場合も既存のものを更新する場合も、サーバー間通知のエンドポイントを提供しなければなりません。3 Appleがこれを発表したのは2025年10月9日でした。
要件の適用範囲は狭く、これから備えるものというより、すでに数か月にわたって効力を持っているものです。興味深いのはその方向性です。Appleは少なくともひとつの法域でエンドポイントを必須にし始めました。その理由は一般化できるものです。共有した個人データに対する主導権を人々に与えること、そしてアカウント削除を実際に波及させること。この論理に韓国固有の要素は何ひとつありません。
どのみち作るのなら、規制当局が期限を切る前に作っておきましょう。
押さえておきたい点
バックエンドエンジニアへ:
- 扱うのは3種類ではなく4種類です。email-enabledとemail-disabledは別々に届きます。
- JWSの検証器に渡す前に、JSONのボディをパースしてpayloadを取り出してください。
- eventsクレームを読む前に、ヘッダーのalgにあるアルゴリズムで署名を検証してください。
- ハンドラーは冪等にし、自前のレコードと突き合わせましょう。Appleは配信保証を文書化していません。
iOSチームへ:
- consent-revokedは資格情報を無効にします。設定の更新ではなく、セッションを終わらせる認証イベントとして扱ってください。
- Apple Accountの削除時、ネイティブアプリにクライアント側のコールバックは届きません。エンドポイントがなければ、それが起きたことを知る手段はありません。
やるべきか迷っている人へ: - 韓国に拠点を置く開発者にとっては2026年1月時点ですでに必須であり、その根拠は一般化できるものです。
よくある質問
通知タイプはいくつありますか
4つです。email-enabled、email-disabled、consent-revoked、account-deleted。1 Appleの開発者向けニュースのアナウンスは3つと説明しており、メール系の2つを転送設定というひとつの項目にまとめています。3
consent-revokedはセッションにどう影響しますか
ユーザーの資格情報が無効になります。1 取り消されたOAuthの認可と同じように扱ってください。設定を更新して続行するのではなく、セッションを終了してユーザーを再認証へ送ります。
ネイティブiOSアプリしか出していない場合もエンドポイントは必要ですか
Appleは、Apple Accountが完全に削除されたとき、ネイティブアプリにはシステムからクライアント側のコールバックが送られないと述べています。1 サーバー間エンドポイントがなければ、それを知らせる仕組みは存在しません。
エンドポイントは何を返すべきですか。停止していた場合はどうなりますか
ステータスコードの期待値、再送のふるまい、重複配信の有無について、Appleは文書化していません。at-least-onceあるいはat-most-onceの配信を想定して設計し、ハンドラーを冪等にし、すべての通知が届いた前提を置かずに自前のレコードと突き合わせてください。
ひとつのエンドポイントで複数のアプリをまかなえますか
Appleのドキュメントは、同じURLを複数のデベロッパチームやアプリで使ってよいとしています。1 登録は、プライマリApp IDにおいて、Sign in with Appleのアプリグループとキーの組み合わせごとに1つのURLです。2 各通知がどのアプリに関するものかをハンドラーが判別できるなら、共有サービスでも機能します。
出典
-
Apple, “Processing changes for Sign in with Apple accounts.” 4つのイベントタイプ(
email-enabled、email-disabled、consent-revoked、account-deleted)、JWSのペイロード形式とヘッダーのalgパラメーターを用いて検証せよという指示、{"payload": "<JWS>"}という包み方、同意の取り消しによって資格情報が無効になるという記述、アカウント削除時にネイティブアプリはクライアント側のコールバックを受け取らないという注記、TLS 1.2のサーバー要件、そして複数のチームとアプリで1つのURLを使ってよいという許容についての出典。2026-08-02取得。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, “Enabling server-to-server notifications.” Certificates, Identifiers & Profilesを経由する登録手順、アプリグループとキーごとに1つのURLというルール、プライマリApp IDに限るという制限、絶対URIの要件、TLS 1.2の要件についての出典。2026-08-02取得。 ↩↩↩
-
Apple Developer News, “New requirement for apps using Sign in with Apple for account creation,” 2025年10月9日。2026年1月1日に発効した韓国の要件、およびエンドポイントが受け取るものの3項目の要約についての出典。 ↩↩↩