Saltar al contenido
Integración

APNs para pases digitales: por qué el push no actualiza el pase

Un push de pase válido viaja casi vacío: aps con content-available al topic del Pass Type ID. PayloadEmpty, MissingTopic y BadDeviceToken, con sus causas.

Por Magdi Khalifah6 min de lecturaActualizado

El push que actualiza un pase de Apple Wallet no lleva contenido: es un aviso silencioso — {"aps":{"content-available":1}} — enviado al topic del Pass Type ID, dirigido al pushToken que el dispositivo registró al instalar. Su único trabajo es disparar la pregunta «¿qué cambió?».

Por eso, cuando «el push no funciona», el diagnóstico tiene tres sospechosos en orden: el payload, el topic y la cadena de web service que viene después del push. Estos son los tres, con los errores exactos que devuelve APNs y las cicatrices que nos dejaron.

Cómo es el push de un pase por dentro

La receta completa, con autenticación por token (una llave .p8 de APNs, no certificados por app — por eso rotar el certificado de firma no toca el push):

  • Payload: {"aps":{"content-available":1}}. Nada más — sin alert, sin badge, sin sound.
  • Header apns-topic: el passTypeIdentifier del pase (por ejemplo pass.com.tuorganizacion.pases). Con autenticación por token, el topic es obligatorio en cada notificación.
  • apns-push-type: background, con prioridad 5 — la combinación que Apple pide para push de contenido silencioso.
  • Destino: el pushToken que el dispositivo entregó al registrarse en tu web service.

Cualquier push entregado a ese topic hace que Wallet consulte tu servidor. No hay mensaje que redactar: la actualización es el contenido nuevo del pase, no el push.

PayloadEmpty: el push que sale vacío sin que lo notes

El rechazo PayloadEmpty significa que APNs recibió un body literalmente vacío — {} — y lo descartó. Suena imposible («yo sí armé el push») y por eso es traicionero: la causa suele estar en la librería, no en tu intención.

Nos pasó con node-apn (@parse/node-apn 7.x): la librería compila el payload como {...payload, aps: apsPayload()}, y apsPayload() devuelve undefined cuando el diccionario aps no tiene claves. Un push armado sin contentAvailable explícito serializa al literal {} — y APNs lo rechaza, además, porque un push background exige content-available: 1. El resultado en nuestra flota, antes del arreglo: todos los push de pase salían vacíos y rechazados, con los pases quedándose viejos en silencio.

El arreglo es una línea (contentAvailable = true) y una lección de arquitectura: el push de pase se construye en un solo lugar del código, no copiado en cada emisor.

MissingTopic: el header que las copias a mano olvidan

MissingTopic significa que la notificación llegó sin apns-topic, y con autenticación por token APNs no puede inferirlo: rechaza. La trampa operativa es que este error nace de la duplicación: cuando el armado del push vive copiado en varios flujos — la campaña, el recordatorio, la sincronización — alguna copia olvida el topic y ese flujo entero deja de actualizar pases, mientras los demás funcionan.

En nuestro caso, cuando consolidamos el armado del push encontramos seis copias del mismo bloque, y cuatro no ponían el topic. La moraleja no es «revisa el topic»: es que el push de pase merece un constructor canónico único, con el contrato topic + prioridad + content-available escrito una sola vez.

BadDeviceToken: casi siempre es el entorno

BadDeviceToken con pases suele significar una sola cosa: tu proveedor APNs apunta al entorno equivocado. APNs rechaza tokens que no pertenecen al entorno al que envías — un token registrado en producción, enviado por la conexión de sandbox, es «bad» aunque sea perfectamente real.

En nuestra operación, todos los tokens que Wallet registra viven en el entorno de producción de APNs; por eso nuestro proveedor apunta a producción por defecto, con un guard explícito contra el fallback silencioso a sandbox — el modo de fallo que quieres evitar es el de la configuración que «funciona» en tu máquina y muere callada en producción.

Su primo Unregistered (HTTP 410) sí es terminal: el token dejó de ser válido — el titular eliminó el pase o restauró el teléfono. La respuesta correcta es depurar ese registro de tu base, no reintentar.

El push salió bien y el pase sigue viejo: audita la cadena, no el push

APNs aceptó la notificación y el pase no cambia. Aquí el error casi nunca está en el push — está en la cadena que el push dispara. En orden de probabilidad, según nuestros incidentes:

  1. El listado de pases actualizados responde 401. El endpoint que lista serials no lleva header Authorization por especificación; si tu servidor lo exige ahí, el dispositivo pregunta, recibe 401 y nunca descarga. El contrato completo está en el contrato webServiceURL.
  2. El cursor no avanza. Si lastUpdated no cambia cuando el contenido cambia, el listado responde vacío y el dispositivo concluye que no hay nada nuevo.
  3. Un 304 indebido. Comparar If-Modified-Since sin truncar a segundos — o responder 304 a un pase recién anulado — deja al dispositivo con la versión vieja creyendo que está al día (la entrega del void, en detalle).
  4. El pase estaba abierto en pantalla durante el push. El render puede tardar en refrescarse; deslizar hacia abajo sobre el pase fuerza la consulta. Es un falso negativo clásico de las pruebas en vivo.
  5. El dispositivo estaba offline. El estado converge al reconectar; en una prueba de escritorio esto se confunde fácil con «no llegó».

El checklist de diagnóstico, en orden

Cuando un pase no se actualiza, este es el orden que más rápido encuentra la causa:

  1. Lee la respuesta del proveedor APNs, no solo «envié»: la respuesta trae aceptados y rechazados con su razón exacta (PayloadEmpty, MissingTopic, BadDeviceToken, Unregistered).
  2. Verifica el payload serializado real — no el que crees que armaste: búscale el content-available.
  3. Llama tú mismo el listado de serials del dispositivo afectado: ¿responde 200 con el serial esperado y sin exigir Authorization?
  4. Descarga el pase por el endpoint autenticado: ¿responde 200 con Last-Modified fresco?
  5. Solo entonces mira el dispositivo: pase abierto, conexión, y el endpoint de log de tu web service — iOS escribe ahí quejas legibles cuando el contrato le molesta.

Lo que hace útil este orden es que cada paso descarta una capa completa: proveedor, payload, listado, contenido, dispositivo. El error de novato — que también cometimos — es empezar por el dispositivo y pasar una tarde reinstalando pases cuando el 401 estaba en el servidor.

Preguntas frecuentes

¿Puedo enviar un push de pase con texto visible?

No por esta vía. El push de un pase es silencioso por diseño; el texto que el titular ve en pantalla sale del changeMessage declarado en el campo del pase que cambió, no del push. Si necesitas comunicar algo que ningún campo refleja, el cambio va primero al contenido del pase.

¿Apple limita cuántos push de pase puedo enviar al día?

Apple no publica una cuota visible al estilo de Google Wallet (3 mensajes por objeto cada 24 horas). El push de pase es silencioso, así que el exceso no molesta al titular — presiona tu servidor, que paga cada re-descarga. La disciplina editorial vive en los changeMessage, no en el push.

¿El push de actualización funciona en el iOS Simulator?

En nuestra experiencia, no: el ciclo completo push, pregunta y re-descarga solo lo hemos verificado en dispositivo físico. El simulador sirve para el flujo de instalación y el render del pase; para validar actualizaciones remotas, usa un iPhone real.

¿Qué pasa si el titular tiene el pase en dos dispositivos?

Cada dispositivo se registra por separado con su propio pushToken, así que el push se envía una vez por registro. Conviene que la clave de idempotencia del envío incluya el pushToken: el mismo pase con dos dispositivos son dos entregas legítimas, no un duplicado.

Sigue leyendo

Todas las guías