O Sign in with Apple envia quatro notificações, não três
O comunicado da Apple para desenvolvedores sobre as notificações servidor a servidor do Sign in with Apple lista três coisas que o seu endpoint vai receber: mudanças nas preferências de encaminhamento de e-mail, exclusões de conta no seu app e exclusões permanentes de Apple Account.3 Já a documentação da API define quatro tipos de evento distintos.1
Uma implementação escrita a partir do comunicado trata três ramificações e descarta silenciosamente um quarto evento. O comunicado funde email-enabled e email-disabled em um único item sobre preferências de encaminhamento. Os dois chegam como notificações separadas, com valores de tipo separados, e um código que ramifica pelo tipo sem um caso padrão ignora justamente aquele que o autor esqueceu.
Dois dos quatro eventos também significam mais do que o nome sugere, e um deles altera o estado de autenticação do seu app.
Resumo rápido
O Sign in with Apple entrega quatro tipos de notificação servidor a servidor: email-enabled, email-disabled, consent-revoked e account-deleted.1 O payload chega como um JWS encapsulado dentro de um objeto JSON sob a chave payload, assinado pela chave privada da Apple, e precisa ser validado com o algoritmo indicado no parâmetro alg do cabeçalho antes que você faça qualquer coisa com ele.1 O consent-revoked invalida as credenciais do usuário, o que faz dele um evento de autenticação, não uma mudança de preferência. Apps nativos não recebem nenhum callback do lado do cliente quando uma Apple Account é excluída, então a notificação no servidor é o único sinal.1 A Apple não documenta nada sobre reenvio ou semântica de entrega.
Os quatro tipos de evento
Cada notificação carrega um valor type dentro da claim events.1
type |
O que aconteceu |
|---|---|
email-enabled |
O usuário ativou o encaminhamento de e-mail para o endereço pessoal dele usando o Hide My Email |
email-disabled |
O usuário desativou o encaminhamento de e-mail |
consent-revoked |
O usuário revogou o consentimento para o seu app, e as credenciais dele deixaram de ser válidas |
account-deleted |
O usuário pediu a exclusão permanente da Apple Account dele |
Os dois eventos de e-mail são os que o comunicado junta. Eles importam de forma independente: email-disabled significa que a mensagem enviada para o endereço de retransmissão para de chegar ao usuário, e email-enabled significa que ela volta a chegar. Tratar os dois como um único evento de “preferências alteradas” obriga você a ir perguntar qual é o estado atual — coisa que a notificação já tinha dito.
Dois eventos que não são o que parecem
consent-revoked é um evento de autenticação. A descrição da Apple é a de que o usuário “revoga o consentimento para o seu app usar a Apple Account dele, e suas credenciais deixam de ser válidas.”1 Não é depreciado nem prestes a expirar. É inválido.
Um app que registra isso junto com os eventos de e-mail e atualiza uma linha de preferências vai continuar exibindo uma interface de usuário autenticado sustentada por credenciais que não autenticam mais. O usuário enxerga a conta dele até a próxima renovação de token falhar — e aí enxerga algo pior. O tratamento correto é encerrar a sessão e encaminhar para uma nova autenticação, no mesmo caminho de código que você usaria para uma concessão OAuth revogada.
account-deleted pode ser o único aviso que você recebe. A Apple afirma que, quando um usuário exclui permanentemente a Apple Account dele, o Sign in with Apple invalida todos os tokens do usuário e desativa o encaminhamento de e-mail em todos os apps associados, e que, para apps nativos, o sistema não envia um callback do lado do cliente.1
Essa frase é o argumento mais forte para manter um endpoint no ar. Um time só de iOS, sem endpoint servidor a servidor, não tem nenhum mecanismo para descobrir que a conta sumiu. O registro continua lá, o endereço de retransmissão para de funcionar, e qualquer obrigação de exclusão que você tenha deixa de ser cumprida porque nada avisou que havia algo a excluir.
Registrando o endpoint
A configuração acontece em Certificates, Identifiers & Profiles: selecione Identifiers, escolha o seu App ID, ative o serviço Sign in with Apple, clique em Configure e informe a URL do endpoint.2
Vale ler as restrições antes de projetar qualquer coisa em torno delas.2
- Uma URL por agrupamento de apps do Sign in with Apple e por chave. Não é uma por app.
- Registrável apenas em um App ID primário.
- A URL precisa ser um URI absoluto com esquema, host e caminho:
https://example.com/path/to/endpoint - TLS 1.2 ou superior é obrigatório para receber notificações.
A documentação da API da Apple acrescenta que você pode usar a mesma URL para vários times de desenvolvimento e apps.1 Combinada com a regra de uma URL por agrupamento, a leitura sensata é a de que um único serviço pode receber tudo, enquanto cada agrupamento registra o próprio ponteiro para ele. A Apple não detalha essa interação, então trate um endpoint compartilhado como algo viável, não como algo oficialmente chancelado — e garanta que o seu handler consiga dizer a qual app cada notificação se refere antes de depender disso.
O piso de TLS 1.2 se conecta a uma mudança mais ampla. O OS 27 passou a impor requisitos de TLS mais rígidos no tráfego de gerenciamento, com o mesmo mínimo de 1.2, além de ciphersuites e certificados compatíveis com ATS. Um endpoint que atende à exigência da Apple hoje não é automaticamente compatível com ATS, e a direção do movimento é para mais rigor, não menos.
Lendo o payload
A entrega chega como um POST HTTP cujo corpo é um objeto JSON, com o token assinado dentro dele:1
{
"payload": "<SERVER_TO_SERVER_NOTIFICATION_JWS>"
}
O JWS vem encapsulado, não cru. Faça o parse do JSON, extraia payload e só então valide. Uma implementação que entrega o corpo inteiro da requisição a um verificador de JWS falha logo na primeira notificação, e a falha se apresenta como token malformado em vez de erro de encapsulamento — o que manda você procurar no lugar errado.
A validação vem antes da interpretação. O payload é assinado criptograficamente pela chave privada da Apple no formato JSON Web Signature, e a instrução da Apple é examinar o JWS e usar o algoritmo especificado no parâmetro alg do cabeçalho para validar a assinatura.1 Só depois que a assinatura confere é que você lê a claim events e ramifica pelo type.
Dois hábitos que vale a pena trazer da prática geral com JWS: nunca confie em um valor de alg que permita a quem chama rebaixar a verificação, e confirme que emissor e audiência do token batem com o que você espera, em vez de aceitar qualquer token bem formado assinado pela Apple.
O formato decodificado
Depois de validada, uma notificação consent-revoked decodificada tem esta cara: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
}
}
O account-deleted traz os mesmos campos com um type diferente. Os eventos de e-mail acrescentam mais dois, email e is_private_email.
Três detalhes desse formato vão custar tempo se você topar com eles de surpresa.
events é um objeto, não um array. O nome está no plural e o valor é um único evento. Código escrito com base no nome, e não no formato, itera um dicionário e recebe chaves.
is_private_email é uma string. Os exemplos da Apple mostram "true" entre aspas, não o booleano true do JSON. Um decodificador estrito que mapeia isso para Bool falha, e um permissivo que trata qualquer string não vazia como verdadeira acerta pelo motivo errado — e depois erra com "false".
sub é o identificador estável do usuário, o mesmo valor que você recebeu no login, e é por ele que você encontra a conta a que a notificação se refere. aud é o identificador do seu cliente, e é o que permite a um endpoint compartilhado rotear notificações de vários apps.
Uma observação sobre os próprios exemplos da Apple: falta uma vírgula entre "is_private_email": "true" e "event_time" nos dois payloads de e-mail. Copie qualquer um dos blocos para um parser JSON e ele vai rejeitar o documento. A estrutura está certa, a pontuação não, e quem colar isso em uma fixture de teste perde dez minutos com um erro de sintaxe que não é dele.
A terminologia da Apple também oscila. O texto descreve um payload no formato JSON Web Signature, enquanto o exemplo do encapsulamento nomeia o valor como SERVER_TO_SERVER_NOTIFICATION_JWT.1 É o mesmo objeto: um JWT assinado é um JWS com payload JSON. Vale saber quando você pesquisar na documentação deles e encontrar os dois termos.
Um handler de ponta a ponta
O formato de um handler correto decorre das restrições acima. Em Python, com 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
Algumas dessas linhas sustentam o resto.
Fixe o algoritmo. O JWKS ativo da Apple publica hoje três chaves RSA, todas RS256, com use: sig e valores de kid distintos. Passar algorithms=["RS256"] em vez de ler o alg de dentro do token fecha o clássico caminho de rebaixamento em que um atacante fornece um token declarando alg: none.
Resolva pelo kid, não pegando a primeira chave. Três chaves ficam ativas ao mesmo tempo, e é essa a cara da rotação de chaves vista de fora. Um handler que pega keys[0] funciona até a Apple rotacionar e então falha para uma parte dos tokens — uma tortura de depurar.
Verifique aud e iss. Sem isso, você aceita qualquer token da Apple validamente assinado, inclusive um emitido para outro app.
Mantenha um caso padrão. Um quinto tipo de evento simplesmente desapareceria. É exatamente assim que uma implementação escrita a partir do comunicado de três itens da Apple perde email-enabled ou email-disabled hoje.
O que a Apple não documenta
Nem a documentação da API nem a página de ajuda sobre a conta dizem nada sobre garantias de entrega. Buscar nas duas por retry, redelivery, acknowledgment, status codes, timeouts e idempotency não retorna nada.
Ou seja, o seguinte fica sem resposta na documentação da Apple no momento em que este texto é escrito:
- Se uma entrega que falha é reenviada, e quantas vezes
- Em que janela de tempo os reenvios acontecem, se é que acontecem
- Que código de status o seu endpoint deve retornar para sinalizar sucesso
- Se a mesma notificação pode chegar duas vezes
Essa ausência tem consequência de projeto — e aqui eu estou raciocinando além do que a Apple afirma, em vez de apenas reportar. Um endpoint cuja semântica de entrega não está especificada não pode ser tratado como um fluxo de eventos definitivo. A postura defensiva é tratar cada notificação como um indício de que algo mudou e reconciliar com os seus próprios registros, em vez de aplicar o evento às cegas. Faça handlers idempotentes, porque você não tem como descartar a hipótese de duplicatas. Não construa um fluxo cuja corretude dependa de ter recebido todas as notificações, porque você não tem como confirmar que recebeu.
Se o seu endpoint ficar fora do ar por uma hora, a documentação não diz se você perdeu uma hora de exclusões de conta ou se elas estão enfileiradas em algum lugar. Projete como se tivesse perdido.
Também não há forma documentada de testar
Buscar nas duas páginas por menções a ambiente de testes, simulação ou disparo manual não retorna nada. A Apple não documenta nenhum mecanismo para disparar uma notificação sob demanda.
O que deixa um ciclo desconfortável. Os eventos que mais importam, consent-revoked e account-deleted, são produzidos por um usuário revogando o acesso ao seu app ou excluindo a Apple Account dele. Verificar o seu handler contra um account-deleted real significa alguém excluir uma Apple Account. Não é um teste que se roda duas vezes.
O substituto viável é dividir o problema. Construa você mesmo os payloads decodificados a partir dos formatos acima e teste unitariamente a ramificação, a idempotência e a lógica de reconciliação contra eles. Em separado, teste o transporte e o caminho da assinatura com uma notificação real que você consiga de fato gerar: revogar o consentimento de uma Apple Account de teste é recuperável de um jeito que excluir uma não é, e isso exercita o parse do encapsulamento, a busca pelo kid e a checagem de assinatura de ponta a ponta.
Faça o que fizer, confirme que o endpoint está acessível e responde rápido antes de registrá-lo. Endpoint registrado e nunca verificado é como um time descobre, meses depois, que toda notificação desde o lançamento foi parar em uma URL atrás de um certificado vencido.
Uma exigência que já está valendo
O motivo de isso ter aparecido nas notícias para desenvolvedores: desde 1º de janeiro de 2026, desenvolvedores sediados na República da Coreia precisam fornecer um endpoint de notificação servidor a servidor ao registrar um novo Services ID ou atualizar um existente, para associar um site a um app usando o Sign in with Apple.3 A Apple anunciou isso em 9 de outubro de 2025.
A exigência é restrita e está em vigor há meses, em vez de ser algo para se preparar. O que interessa nela é a direção que aponta. A Apple começou a tornar o endpoint obrigatório em pelo menos uma jurisdição, por razões que se generalizam: dar às pessoas controle sobre os dados pessoais que compartilharam e fazer a exclusão de conta de fato se propagar. Nada nessa lógica é específico da Coreia.
Se você vai construir o endpoint de qualquer jeito, construa antes que um regulador transforme isso no seu prazo.
Pontos principais
Para engenheiros de backend:
- Trate quatro tipos, não três. email-enabled e email-disabled chegam separadamente.
- Faça o parse do corpo JSON e extraia payload antes de entregar qualquer coisa a um verificador de JWS.
- Valide a assinatura com o algoritmo indicado no alg do cabeçalho antes de ler a claim events.
- Faça handlers idempotentes e reconcilie com os seus próprios registros. A Apple não documenta nenhuma garantia de entrega.
Para times de iOS:
- consent-revoked invalida credenciais. Trate como um evento de autenticação que encerra a sessão, não como atualização de preferência.
- Apps nativos não recebem callback do lado do cliente na exclusão de uma Apple Account. Sem um endpoint, você nunca fica sabendo que aconteceu.
Para quem está avaliando se vale o trabalho: - O endpoint já é obrigatório para desenvolvedores sediados na Coreia desde janeiro de 2026, e a justificativa se generaliza.
Perguntas frequentes
Quantos tipos de notificação existem?
Quatro: email-enabled, email-disabled, consent-revoked e account-deleted.1 O comunicado da Apple no canal de notícias para desenvolvedores descreve três, fundindo os dois eventos de e-mail em um único item sobre preferências de encaminhamento.3
O que o consent-revoked significa para a minha sessão?
As credenciais do usuário deixam de ser válidas.1 Trate como você trataria uma concessão OAuth revogada: encerre a sessão e mande o usuário para uma nova autenticação, em vez de atualizar uma preferência e seguir em frente.
Preciso de um endpoint se eu só publico um app iOS nativo?
A Apple afirma que, para apps nativos, o sistema não envia um callback do lado do cliente quando uma Apple Account é excluída permanentemente.1 Sem um endpoint servidor a servidor, não existe mecanismo que informe você.
O que o meu endpoint deve retornar, e o que acontece se ele estiver fora do ar?
A Apple não documenta expectativas de código de status, comportamento de reenvio nem se ocorrem entregas duplicadas. Projete para entrega pelo menos uma vez — ou possivelmente no máximo uma vez —, faça handlers idempotentes e reconcilie com os seus próprios registros, em vez de supor que toda notificação chegou.
Um único endpoint pode atender a vários apps?
A documentação da Apple diz que você pode usar a mesma URL para vários times de desenvolvimento e apps.1 O registro é de uma URL por agrupamento de apps do Sign in with Apple e por chave, em um App ID primário.2 Um serviço compartilhado funciona, desde que o seu handler consiga determinar a qual app cada notificação se refere.
Fontes
-
Apple, “Processing changes for Sign in with Apple accounts.” Fonte para os quatro tipos de evento (
email-enabled,email-disabled,consent-revoked,account-deleted), o formato do payload JWS e a instrução de validar usando o parâmetroalgdo cabeçalho, o encapsulamento{"payload": "<JWS>"}, a afirmação de que o consentimento revogado torna as credenciais inválidas, a nota de que apps nativos não recebem callback do lado do cliente na exclusão da conta, a exigência de TLS 1.2 no servidor e a permissão de usar uma única URL em vários times e apps. Consultado em 2 de agosto de 2026. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, “Enabling server-to-server notifications.” Fonte para o caminho de registro em Certificates, Identifiers & Profiles, a regra de uma URL por agrupamento de apps e chave, a restrição ao App ID primário, a exigência de URI absoluto e a exigência de TLS 1.2. Consultado em 2 de agosto de 2026. ↩↩↩
-
Apple Developer News, “New requirement for apps using Sign in with Apple for account creation,” 9 de outubro de 2025. Fonte para a exigência da Coreia em vigor desde 1º de janeiro de 2026 e para o resumo em três itens do que o endpoint recebe. ↩↩↩