Sign in with Apple envía cuatro notificaciones, no tres
El anuncio para desarrolladores de Apple sobre las notificaciones servidor a servidor de Sign in with Apple enumera tres cosas que recibirá tu endpoint: cambios en las preferencias de reenvío de correo, eliminaciones de cuenta dentro de tu app y eliminaciones permanentes de la Apple Account.3 La documentación de la API define cuatro tipos de evento distintos.1
Una implementación escrita a partir del anuncio maneja tres ramas y descarta en silencio un cuarto evento. El anuncio agrupa email-enabled y email-disabled en una sola viñeta sobre las preferencias de reenvío. En realidad llegan como notificaciones separadas, con valores de tipo diferentes, y el código que hace un switch sobre el tipo sin un caso por defecto ignora el que el autor haya olvidado.
Dos de los cuatro eventos, además, significan más de lo que sugiere su nombre, y uno de ellos cambia el estado de autenticación de tu app.
En resumen
Sign in with Apple entrega cuatro tipos de notificación servidor a servidor: email-enabled, email-disabled, consent-revoked y account-deleted.1 El payload llega como un JWS envuelto dentro de un objeto JSON bajo la clave payload, firmado con la clave privada de Apple, y hay que validarlo con el algoritmo indicado en el parámetro alg de la cabecera antes de actuar sobre él.1 consent-revoked invalida las credenciales del usuario, lo que lo convierte en un evento de autenticación y no en un cambio de preferencias. Las apps nativas no reciben ninguna llamada de retorno del lado del cliente cuando se elimina una Apple Account, de modo que la notificación al servidor es la única señal.1 Apple no documenta ninguna semántica de reintentos ni de entrega.
Los cuatro tipos de evento
Cada notificación lleva un valor type dentro del claim events.1
type |
Qué ocurrió |
|---|---|
email-enabled |
El usuario activó el reenvío de correo a su dirección personal mediante Hide My Email |
email-disabled |
El usuario desactivó el reenvío de correo |
consent-revoked |
El usuario revocó el consentimiento para tu app y sus credenciales dejaron de ser válidas |
account-deleted |
El usuario solicitó la eliminación permanente de su Apple Account |
Los dos eventos de correo son los que el anuncio fusiona. Importan por separado: email-disabled significa que el correo que envías a la dirección de retransmisión deja de llegar al usuario, y email-enabled significa que vuelve a llegar. Tratarlos como un único evento de “cambio de preferencias” te obliga a ir a preguntar cuál es el estado actual, algo que la notificación ya te había dicho.
Dos eventos que no son lo que parecen
consent-revoked es un evento de autenticación. La descripción de Apple dice que el usuario “revoca el consentimiento para que tu app use su Apple Account y sus credenciales dejan de ser válidas”.1 No obsoletas, no a punto de caducar. Inválidas.
Una app que registre esto junto con los eventos de correo y actualice una fila de preferencias seguirá mostrando una interfaz con la sesión iniciada, respaldada por credenciales que ya no autentican. El usuario ve su cuenta hasta que falla la siguiente renovación del token, y entonces ve algo peor. El manejo correcto es cerrar la sesión y redirigir a una nueva autenticación, por la misma ruta de código que usarías ante una concesión OAuth revocada.
account-deleted puede ser el único aviso que recibas. Apple indica que, cuando un usuario elimina permanentemente su Apple Account, Sign in with Apple invalida todos los tokens de ese usuario y desactiva el reenvío de correo en todas las apps asociadas, y que en el caso de las apps nativas el sistema no envía ninguna llamada de retorno del lado del cliente.1
Esa frase es el argumento más fuerte para tener un endpoint funcionando. Un equipo que solo publica en iOS y no tiene endpoint servidor a servidor carece de todo mecanismo para enterarse de que la cuenta ya no existe. El registro persiste, la dirección de retransmisión deja de funcionar y cualquier obligación de eliminación que tengas queda incumplida, porque nada te avisó de que hubiera algo que eliminar.
Cómo registrar el endpoint
La configuración se hace en Certificates, Identifiers & Profiles: selecciona Identifiers, elige tu App ID, activa el servicio Sign in with Apple, haz clic en Configure e indica la URL del endpoint.2
Vale la pena leer las restricciones antes de diseñar en torno a ellas.2
- Una URL por agrupación de apps de Sign in with Apple y clave. No una por app.
- Solo se puede registrar en un App ID primario.
- La URL debe ser un URI absoluto con esquema, host y ruta:
https://example.com/path/to/endpoint - Se requiere TLS 1.2 o posterior para recibir notificaciones.
La documentación de la API de Apple añade que puedes usar la misma URL para varios equipos de desarrollo y varias apps.1 Leído junto con la regla de una URL por agrupación, la interpretación sensata es que un único servicio puede recibirlo todo mientras cada agrupación registra su propio puntero hacia él. Apple no detalla esa interacción, así que trata un endpoint compartido como algo viable, no como algo avalado, y asegúrate de que tu manejador pueda determinar a qué app se refiere cada notificación antes de depender de él.
Ese requisito de TLS 1.2 conecta con un cambio más amplio. OS 27 empezó a aplicar requisitos de TLS más estrictos al tráfico de gestión, con el mismo mínimo de 1.2 más conjuntos de cifrado y certificados compatibles con ATS. Un endpoint que hoy cumple el requisito de Apple no es automáticamente compatible con ATS, y la dirección del movimiento es hacia lo más estricto, no hacia lo más laxo.
Cómo leer el payload
La entrega llega como un HTTP POST cuyo cuerpo es un objeto JSON, con el token firmado dentro:1
{
"payload": "<SERVER_TO_SERVER_NOTIFICATION_JWS>"
}
El JWS viene envuelto, no en crudo. Analiza el JSON, extrae payload y luego valida. Una implementación que entrega el cuerpo completo de la petición a un verificador de JWS falla en la primera notificación, y el fallo se presenta como un token mal formado en lugar de como un error de envoltura, lo que te manda a buscar donde no es.
La validación va antes que la interpretación. El payload está firmado criptográficamente con la clave privada de Apple en formato JSON Web Signature, y la instrucción de Apple es examinar el JWS y usar el algoritmo especificado en el parámetro alg de la cabecera para validar la firma.1 Solo cuando la firma se comprueba correctamente pasas a leer el claim events y a ramificar según type.
Dos costumbres que conviene conservar de la práctica general con JWS: nunca confíes en un valor de alg que permita a quien llama degradar la verificación, y confirma que el emisor y la audiencia del token coinciden con lo que esperas, en vez de aceptar cualquier token bien formado firmado por Apple.
La forma decodificada
Una vez validada, una notificación consent-revoked decodificada se ve así: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 lleva los mismos campos con un type distinto. Los eventos de correo añaden dos más, email y is_private_email.
Tres detalles de esa estructura te costarán tiempo si te los encuentras por sorpresa.
events es un objeto, no un array. El nombre está en plural y el valor es un único evento. El código escrito pensando en el nombre y no en la forma itera un diccionario y obtiene claves.
is_private_email es una cadena. Los ejemplos de Apple muestran "true" entre comillas, no el booleano true de JSON. Un decodificador estricto que lo mapee a un Bool falla, y uno permisivo que trate cualquier cadena no vacía como verdadera acierta por el motivo equivocado y luego se equivoca con "false".
sub es el identificador estable del usuario, el mismo valor que recibiste al iniciar sesión, y es la vía para encontrar la cuenta a la que se refiere la notificación. aud es tu identificador de cliente, que es lo que permite a un endpoint compartido enrutar notificaciones de varias apps.
Un apunte sobre los propios ejemplos de Apple: a los dos payloads de correo les falta una coma entre "is_private_email": "true" y "event_time". Copia cualquiera de esos bloques en un analizador de JSON y rechazará el documento. La estructura está bien, la puntuación no, y quien lo pegue en un fixture de pruebas perderá diez minutos con un error de sintaxis que no es suyo.
La terminología de Apple también fluctúa. La prosa describe un payload en formato JSON Web Signature, mientras que el ejemplo de la envoltura nombra el valor SERVER_TO_SERVER_NOTIFICATION_JWT.1 Se trata del mismo objeto: un JWT firmado es un JWS con un payload JSON. Conviene saberlo cuando busques en su documentación y encuentres ambos términos.
Un manejador, de principio a fin
La forma de un manejador correcto se deduce de las restricciones anteriores. En Python, con 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
Algunas de esas líneas soportan todo el peso.
Fija el algoritmo. El JWKS activo de Apple publica hoy tres claves RSA, todas RS256 con use: sig y valores kid distintos. Pasar algorithms=["RS256"] en lugar de leer alg del token cierra la clásica vía de degradación en la que un atacante entrega un token que declara alg: none.
Resuelve por kid, no tomando la primera clave. Hay tres claves vivas simultáneamente, que es como se ve la rotación de claves desde fuera. Un manejador que agarra keys[0] funciona hasta que Apple rota las claves y entonces falla para un subconjunto de tokens, algo que es una pesadilla depurar.
Verifica aud e iss. Sin ellos aceptas cualquier token de Apple firmado válidamente, incluido uno emitido para otra app.
Conserva una rama por defecto. Un quinto tipo de evento se esfumaría sin ella. Es exactamente así como una implementación escrita a partir del anuncio de tres puntos de Apple pierde hoy email-enabled o email-disabled.
Lo que Apple no documenta
Ni la documentación de la API ni la página de ayuda de la cuenta dicen nada sobre garantías de entrega. Buscar en ambas los términos reintento, reenvío, acuse de recibo, códigos de estado, tiempos de espera e idempotencia no devuelve nada.
Así que, al momento de escribir esto, la documentación de Apple deja sin respuesta lo siguiente:
- Si una entrega fallida se reintenta, y cuántas veces
- En qué ventana de tiempo ocurren los reintentos, si es que ocurren
- Qué código de estado debería devolver tu endpoint para indicar éxito
- Si la misma notificación puede llegar dos veces
Esa ausencia tiene una consecuencia de diseño y, aquí, estoy razonando más allá de lo que Apple afirma en lugar de reportarlo. Un endpoint cuya semántica de entrega no está especificada no puede tratarse como un flujo de eventos fidedigno. La postura defensiva es tratar cada notificación como un indicio de que algo cambió y reconciliar contra tus propios registros en vez de aplicar el evento a ciegas. Haz que los manejadores sean idempotentes, porque no puedes descartar duplicados. No construyas un flujo cuya corrección dependa de haber recibido todas las notificaciones, porque no puedes confirmar que así fuera.
Si tu endpoint está caído durante una hora, la documentación no te dice si perdiste una hora de eliminaciones de cuenta o si están encoladas en algún sitio. Diseña como si las hubieras perdido.
Tampoco hay una forma documentada de probarlo
Buscar en ambas páginas los términos entorno de pruebas, simular y disparar no devuelve nada. Apple no documenta ningún mecanismo para lanzar una notificación bajo demanda.
Eso deja un bucle incómodo. Los eventos que más importan, consent-revoked y account-deleted, los produce un usuario al revocar el acceso a tu app o al eliminar su Apple Account. Verificar tu manejador contra un account-deleted real implica que alguien elimine una Apple Account. Esa no es una prueba que hagas dos veces.
El sustituto viable consiste en dividir el problema. Construye tú mismo los payloads decodificados a partir de las estructuras anteriores y escribe pruebas unitarias que ejerciten contra ellos la ramificación, la idempotencia y la lógica de reconciliación. Aparte, prueba el transporte y la ruta de firma con una notificación real que sí puedas generar: revocar el consentimiento de una Apple Account de prueba es recuperable de un modo en que eliminarla no lo es, y ejercita de principio a fin el análisis de la envoltura, la búsqueda por kid y la comprobación de la firma.
Hagas lo que hagas, confirma que el endpoint es accesible y responde con prontitud antes de registrarlo. Un endpoint registrado y nunca verificado es la forma en que un equipo descubre, meses después, que todas las notificaciones desde el lanzamiento fueron a una URL detrás de un certificado caducado.
Un requisito que ya está vigente
La razón por la que esto apareció en las noticias para desarrolladores: desde el 1 de enero de 2026, los desarrolladores radicados en la República de Corea deben proporcionar un endpoint de notificaciones servidor a servidor al registrar un nuevo Services ID o al actualizar uno existente, para poder asociar un sitio web con una app mediante Sign in with Apple.3 Apple lo anunció el 9 de octubre de 2025.
El requisito es acotado y lleva meses en vigor: no es algo para lo que haya que prepararse, sino algo que ya está aquí. Lo interesante es la dirección que marca. Apple ha empezado a hacer obligatorio el endpoint en al menos una jurisdicción, por razones que se generalizan: dar a las personas control sobre los datos personales que han compartido y lograr que la eliminación de cuentas se propague de verdad. Nada de esa lógica es exclusivo de Corea.
Si vas a construir el endpoint de todos modos, constrúyelo antes de que un regulador te ponga la fecha límite.
Puntos clave
Para ingenieros de backend:
- Maneja cuatro tipos, no tres. email-enabled y email-disabled llegan por separado.
- Analiza el cuerpo JSON y extrae payload antes de entregar nada a un verificador de JWS.
- Valida la firma con el algoritmo del parámetro alg de la cabecera antes de leer el claim events.
- Haz los manejadores idempotentes y reconcilia contra tus propios registros. Apple no documenta ninguna garantía de entrega.
Para equipos de iOS:
- consent-revoked invalida las credenciales. Trátalo como un evento de autenticación que termina la sesión, no como una actualización de preferencias.
- Las apps nativas no reciben ninguna llamada de retorno del lado del cliente cuando se elimina una Apple Account. Sin un endpoint, nunca llegas a enterarte de que ocurrió.
Para quien esté sopesando si vale la pena: - El endpoint ya es obligatorio para los desarrolladores radicados en Corea desde enero de 2026, y el razonamiento se generaliza.
Preguntas frecuentes
¿Cuántos tipos de notificación hay?
Cuatro: email-enabled, email-disabled, consent-revoked y account-deleted.1 El anuncio de Apple Developer News describe tres, al fusionar los dos eventos de correo en una sola viñeta sobre las preferencias de reenvío.3
¿Qué significa consent-revoked para mi sesión?
Que las credenciales del usuario dejan de ser válidas.1 Trátalo como tratarías una concesión OAuth revocada: cierra la sesión y envía al usuario a autenticarse de nuevo, en lugar de actualizar una preferencia y seguir adelante.
¿Necesito un endpoint si solo publico una app nativa de iOS?
Apple indica que, en el caso de las apps nativas, el sistema no envía ninguna llamada de retorno del lado del cliente cuando se elimina permanentemente una Apple Account.1 Sin un endpoint servidor a servidor no hay mecanismo alguno que te informe.
¿Qué debería devolver mi endpoint, y qué pasa si está caído?
Apple no documenta qué códigos de estado espera, ni el comportamiento de reintentos, ni si se producen entregas duplicadas. Diseña para una entrega «al menos una vez», o quizá «como máximo una vez», haz los manejadores idempotentes y reconcilia contra tus propios registros en lugar de suponer que llegaron todas las notificaciones.
¿Puede un mismo endpoint servir a varias apps?
La documentación de Apple dice que puedes usar la misma URL para varios equipos de desarrollo y varias apps.1 El registro es de una URL por agrupación de apps de Sign in with Apple y clave, sobre un App ID primario.2 Un servicio compartido funciona, siempre que tu manejador pueda determinar a qué app corresponde cada notificación.
Fuentes
-
Apple, “Processing changes for Sign in with Apple accounts.” Fuente de los cuatro tipos de evento (
email-enabled,email-disabled,consent-revoked,account-deleted), del formato del payload JWS y de la instrucción de validar usando el parámetroalgde la cabecera, de la envoltura{"payload": "<JWS>"}, de la afirmación de que el consentimiento revocado invalida las credenciales, de la nota de que las apps nativas no reciben ninguna llamada de retorno del lado del cliente al eliminarse la cuenta, del requisito de TLS 1.2 en el servidor y de la autorización para usar una sola URL en varios equipos y apps. Consultado el 2 de agosto de 2026. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, “Enabling server-to-server notifications.” Fuente de la ruta de registro a través de Certificates, Identifiers & Profiles, de la regla de una URL por agrupación de apps y clave, de la restricción del App ID primario, del requisito de URI absoluto y del requisito de TLS 1.2. Consultado el 2 de agosto de 2026. ↩↩↩
-
Apple Developer News, “New requirement for apps using Sign in with Apple for account creation,” 9 de octubre de 2025. Fuente del requisito para Corea vigente desde el 1 de enero de 2026 y del resumen de tres puntos sobre lo que recibe el endpoint. ↩↩↩