Un reembolso en Google Play llega en silencio. El cargo se revierte, el cliente sigue usando la app y nada cambia de tu lado a menos que lo verifiques deliberadamente. Verificarlo es justo para lo que existe la Voided Purchases API. Devuelve los pedidos que fueron cancelados, reembolsados o disputados por contracargo, para que puedas retirar el acceso a lo que el cliente dejó de pagar. Conecta un job programado, lee lo que devuelve y revoca el derecho de acceso. Ese es todo el mecanismo.
Hay algo que atrapa a casi todos los equipos, y no está en el código sino en la política. Esta API solo lista los pedidos que efectivamente fueron revocados. Si reembolsas una compra en la Play Console sin marcar la opción de revocar, ese pedido nunca aparece aquí, así que tu job se ejecuta perfectamente limpio mientras un cliente reembolsado se queda con todo lo que le vendiste. A continuación repasamos la API campo por campo, los límites que la acotan y por dónde se escapa el dinero en silencio cuando no está bien conectada.
Puntos clave
La Voided Purchases API, a la que se accede mediante el método purchases.voidedpurchases.list, lista los pedidos que Google Play canceló, reembolsó o disputó por contracargo, lo que te permite construir un sistema que retira el acceso a compras que el cliente ya no conserva.
Solo muestra los pedidos revocados. Un reembolso emitido por el desarrollador sin revocar permanece invisible aquí, así que retirar el acceso implica reembolsar con la revocación activada.
Su alcance es una ventana móvil de 30 días. Como startTime no puede retroceder más de 30 días, cualquier servidor que esté fuera de línea más de un mes pierde esas anulaciones de forma permanente, por lo que el polling tiene que ejecutarse con un cronograma fijo.
voidedSource identifica quién inició la anulación: 0 para el usuario, 1 para el desarrollador, 2 para Google. voidedReason explica el motivo, y va de 0 para Other hasta 7 para Chargeback y 8 para Unacknowledged_purchase.
Las notificaciones de desarrollador en tiempo real disparan un VoidedPurchaseNotification en el instante en que una compra se anula, pero trátalo como un aviso. Confirma contra la Voided Purchases API antes de revocar nada.
Distingue las renovaciones de suscripción por orderId, nunca por purchaseToken. Un solo purchaseToken abarca todas las renovaciones de una suscripción, así que el token por sí solo no puede separar un período del siguiente.
Los topes son 6,000 consultas por día y 30 dentro de cualquier intervalo de 30 segundos, así que limita cada solicitud a una ventana de tiempo y avanza por ella usando el token de continuación, nunca una solicitud por pedido.
Qué devuelve la API
El endpoint resuelve una sola pregunta: cuáles de los pedidos de esta app se anularon recientemente. Una anulación agrupa tres resultados que devuelven el dinero al cliente, a saber, una cancelación, un reembolso o un contracargo. Alcanza tanto a los productos dentro de la app de una sola compra como a las suscripciones, y un parámetro define el alcance. Deja type en 0, el valor predeterminado, y solo recibirás las compras anuladas de productos dentro de la app. Fija type en 1 y recibirás juntas las compras anuladas dentro de la app y las suscripciones anuladas.
Cada elemento de la respuesta es un objeto de compra anulada con un conjunto breve y de alto valor de campos:
orderId marca de forma única una compra puntual, una suscripción o una renovación individual dentro de ella. Trátalo como tu clave de unión.
purchaseToken identifica una compra única o una suscripción, pero no separa las renovaciones, así que apóyate en orderId para eso.
purchaseTimeMillis registra cuándo ocurrió la compra, en milisegundos desde el epoch.
voidedTimeMillis registra cuándo fue cancelada, reembolsada o disputada por contracargo, también en milisegundos desde el epoch.
voidedSource indica quién inició la anulación, donde 0 es el usuario, 1 es el desarrollador y 2 es Google.
voidedReason da la causa como un entero de 0 a 8.
voidedQuantity lleva la cantidad anulada de un reembolso parcial basado en cantidad, y solo aparece cuando includeQuantityBasedPartialRefund es true.
Como un mismo purchaseToken cubre toda una suscripción mientras que cada renovación recibe un orderId recién generado, si basas tus derechos de acceso en el token terminarás revocando el período equivocado. Usa orderId como clave en su lugar.
Lee voidedReason antes de tocar nada
voidedReason es lo que convierte una simple lista en una decisión real, porque un reembolso por arrepentimiento y un contracargo bancario comparten el mismo feed y sin embargo no se parecen en nada. El conjunto completo es el siguiente:
1. Other no tiene categoría asignada. Revócalo y sigue adelante.
2. Remorse es un cliente que cambió de opinión, un reembolso cotidiano.
3. Not_received es un reclamo de que el producto nunca llegó, algo que vale la pena revisar en tu entrega.
4. Defective significa que no funcionó, una señal de calidad que deberías registrar.
5. Accidental_purchase es una compra no intencionada, con frecuencia en un dispositivo compartido.
6. Fraud es una transacción que Google marcó como fraudulenta.
7. Friendly_fraud es un contracargo en el que el verdadero titular de la tarjeta disputa un cargo que realmente hizo.
8. Chargeback es el banco del cliente revirtiendo el pago, algo definitivo ante el banco y que ahora se te factura a ti.
9. Unacknowledged_purchase es Google reembolsando automáticamente una compra que tu app nunca confirmó.
El motivo 9 es un reembolso que tú mismo provocaste. Cualquier compra que tu app deje sin confirmar recibe su dinero de vuelta por parte de Google, así que un unacknowledged_purchase en este feed es ingreso cedido por una sola llamada omitida, y no por ninguna decisión del comprador.
La ventana de 30 días que vacía la lista en silencio
La API no alcanza más allá de los últimos 30 días. Por defecto, startTime es la hora actual menos 30 días y no acepta un valor anterior, mientras que endTime toma el presente por defecto, así que lo que obtienes es una ventana móvil de un mes y no un archivo permanente.
La implicación es contundente. Si un job de polling se rompe y pasa cinco semanas sin que nadie lo note, las anulaciones de la primera semana ya salieron de la API, sin ninguna llamada capaz de recuperarlas. Esos pedidos quedan sin revocar, y ni siquiera sabrías que ocurrieron a menos que los hubieras capturado en otro lugar. La red tiene un agujero exactamente tan ancho como tu interrupción más larga. Haz polling al menos a diario y concilia contra las notificaciones en tiempo real, tratando los 30 días como el reloj de borrado definitivo que realmente son.
Que un pedido aparezca o no depende de la revocación
Esta es la razón número uno por la que los equipos dicen que la API está rota. Solo devuelve pedidos revocados, nada más. Los reembolsos que inician los usuarios, las cancelaciones, los contracargos y los reembolsos iniciados por Google se revocan de forma automática, así que siempre aparecen en el feed. Un reembolso iniciado por el desarrollador es la excepción. Emitir el reembolso tú mismo, ya sea desde la Play Console o mediante la Orders API, deja la decisión de revocar como algo aparte que tienes que resolver. Si no la tomas, el pedido queda liquidado con el cliente pero nunca aparece en este feed.
La conclusión es breve. Cuando tu objetivo es cortar el acceso, reembolsa con la revocación activada. Si la omites, habrás devuelto el dinero dejando la puerta abierta, y tu job de revocación, por muy bien construido que esté, no tendrá nada con qué trabajar.
Hacer polling sin agotar la cuota
El endpoint tiene un límite de frecuencia, y los topes son lo bastante bajos como para que un bucle descuidado los dispare. Tienes 6,000 consultas al día, contadas en horario del Pacífico, y nunca más de 30 en cualquier lapso de 30 segundos. Ese presupuesto es apto para el polling por ventanas y castiga cualquier diseño de una solicitud por pedido.
Ventanas de tiempo y el token de continuación
maxResults está en 1,000 por defecto, y ese también es el valor máximo posible. Si una ventana contiene más anulaciones de las que cabe una sola página, la respuesta viene con un objeto tokenPagination que contiene un nextPageToken. Devuelve ese token en tu siguiente llamada para avanzar por las páginas. Fija los bordes de la ventana con startTime y endTime, sigue paginando hasta que el token se agote, y solo entonces desplaza la ventana. Ese ritmo se mantiene lejos tanto del tope de ráfaga de 30 segundos como del límite diario.
Las notificaciones en tiempo real cierran el vacío diario
Incluso un polling diario te deja a ciegas hasta por 24 horas, y los vacíos largos son exactamente lo que castiga la ventana de 30 días. Las notificaciones de desarrollador en tiempo real cierran ese retraso. En el instante en que una compra se anula, se publica un VoidedPurchaseNotification en un tema de Cloud Pub/Sub bajo tu control, y tu backend lo consume en cuestión de segundos. Su payload se mantiene compacto:
purchaseToken es el token de la compra original.
orderId es el id de la transacción anulada, generado de nuevo en cada renovación de suscripción.
productType es 1 para una suscripción y 2 para una compra única.
refundType distingue un reembolso total, marcado con 1, de un reembolso parcial basado en cantidad, marcado con 2.
Toma la notificación como una alerta, no como una verdad absoluta. En cuanto llega un VoidedPurchaseNotification, consulta la Voided Purchases API para verificar en qué estado se encuentra realmente el pedido, y revoca solo entonces. La alerta te dice que verifiques; la API te dice la verdad.
Lo que te cuesta en dinero
La API es cañería, pero la razón para tenderla es una factura, y dos de sus cifras van en aumento.
A partir del 3 de agosto de 2026, la factura del contracargo es tuya
A partir del 3 de agosto de 2026, Google traslada el costo de un contracargo al desarrollador. Pierdes el precio de la compra y además cubres la comisión de contracargo del banco. Un voidedReason de 7 deja de ser simplemente una venta perdida y se convierte en un ítem de gasto con una comisión asociada. El contracargo en sí no se puede revertir, ya que es definitivo ante el banco, pero detectar la anulación con rapidez te permite revocar el derecho de acceso y, para todo lo que aún se esté entregando, dejar de gastar en un cliente que fue reembolsado y luego revirtió el pago.
Sigues financiando a un cliente que ya fue reembolsado
El precio de la venta se pierde en el momento en que llega una anulación. Lo que sigues controlando es el gasto de continuar con la entrega. Por cada hora que un derecho de acceso reembolsado permanece activo, las facturas que el cliente dejó de cubrir siguen llegando de tu lado: el cómputo, las llamadas al proveedor del modelo, el almacenamiento y cualquier pago a creadores o socios que su actividad dispare. Construir el pipeline de revocación sobre esta API es cómo se corta ese gasto. Si te la saltas, estás subvencionando el producto para gente a la que la tienda ya reembolsó.
El friendly fraud es una tendencia, no un incidente
Un voidedReason de 5 o 6 rara vez aparece solo. El fraude y el friendly fraud se agrupan alrededor de cuentas, dispositivos y, en ocasiones, promociones particulares. Como la API adjunta voidedSource y voidedReason a cada anulación, tienes suficiente información para detectar tendencias de abuso por cuenta en lugar de absorber cada reversión como una pérdida aislada. Una cuenta que hace un contracargo por segunda vez te está diciendo lo que el primer reembolso no dijo.
Uniendo todo
Todo el modelo es pequeño una vez que tienes las piezas en mano. Escucha VoidedPurchaseNotification en tiempo real para que nada espere un día entero. Trata la Voided Purchases API como la fuente de verdad, con orderId como clave, para que las renovaciones nunca se mezclen. Lee voidedSource y voidedReason para que un contracargo se maneje distinto de un reembolso por arrepentimiento. Haz polling con un ritmo lo bastante ajustado como para que la ventana de 30 días nunca te muerda, y reembolsa con la revocación activada cada vez que tu intención sea cortar el acceso.
Esta es la capa que Refund Sensor gestiona en tu nombre. Consume las notificaciones en tiempo real, concilia cada anulación contra la API, revoca el pedido preciso en lugar del producto completo, y mantiene un contracargo bancario separado de un reembolso normal para que los casos costosos salgan a la luz en vez de esconderse. Obtienes el acceso retirado en cuestión de segundos y un registro completo de quién anuló qué y por qué, sin necesidad de que levantes ningún pipeline de Pub/Sub ni job de polling.
Dónde está documentado todo esto
Preguntas frecuentes
Porque el feed solo incluye pedidos revocados. Los reembolsos que inicia un usuario, junto con las cancelaciones, los contracargos y los reembolsos iniciados por Google, se revocan automáticamente y siempre aparecen. Un reembolso que emites tú mismo solo aparece si además elegiste revocarlo. Si reembolsas sin revocar, el pedido queda liquidado pero invisible aquí, así que activa la revocación siempre que tu objetivo sea retirar el acceso.
Treinta días, y nada más. Por defecto, startTime se ubica en la hora actual menos 30 días y rechaza cualquier valor anterior, de modo que el endpoint funciona como una ventana móvil de un mes en lugar de un archivo histórico. Una vez que una anulación supera los 30 días, desaparece sin forma de recuperarla, y por eso haces polling con un cronograma fijo y lo respaldas con notificaciones en tiempo real.
Ambas cumplen un papel. Un VoidedPurchaseNotification te llega en cuestión de segundos y te indica que verifiques, pero la recomendación de Google es tratarlo como una señal y no como la verdad. Confirma el estado actual mediante la Voided Purchases API y solo entonces revoca. La notificación elimina el retraso; la API aporta el voidedSource y el voidedReason autorizados sobre los que realmente actúas.
Fíjate en voidedReason. Un 7 es un contracargo, en el que el banco del cliente revirtió el pago, y un 6 es friendly fraud. Un 1 es un reembolso normal por cambio de opinión. La distinción importa porque, a partir del 3 de agosto de 2026, Google traslada al desarrollador el precio del contracargo y la comisión del banco, así que un 7 te cuesta más que un reembolso simple.
Sí. Fija el parámetro type en 1 y recibirás las compras anuladas dentro de la app junto con las suscripciones anuladas; el valor predeterminado de 0 devuelve solo los productos dentro de la app. Para las suscripciones, precisa el período exacto anulado mediante orderId, ya que un solo purchaseToken abarca todas las renovaciones mientras que cada transacción de renovación tiene su propio orderId.






