npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kilnjs/payments-play

v0.1.3

Published

Google Play billing for KilnJS: real time notifications verified by the token that carried them, and purchases read from the Android Publisher API

Readme

@kilnjs/payments-play

Google Play billing for KilnJS: real time developer notifications, and purchases read from the Android Publisher API. No SDK.

yarn add @kilnjs/payments @kilnjs/payments-play
// app.config.ts
import { payments } from "@kilnjs/payments";
import "@kilnjs/payments-play";

| option | environment | | | --- | --- | --- | | packageName | PLAY_PACKAGE_NAME | required. Checked against every notification | | audience | PLAY_PUSH_AUDIENCE | required. What the push subscription claims as its audience, usually this route's URL | | pushAccount | PLAY_PUSH_SERVICE_ACCOUNT | required to verify. The service account Pub/Sub pushes as | | clientEmail | PLAY_CLIENT_EMAIL | for reading purchases, which is a different account | | privateKey | PLAY_PRIVATE_KEY | as above | | keysUrl | PLAY_KEYS_URL | where Google publishes its signing keys | | api | PLAY_API_URL | the Android Publisher API | | timeout | PLAY_TIMEOUT_MS | 10000 |

Play is the one that carries no signature

This is the difference that gets shipped as a vulnerability. A Play notification arrives as a plain Pub/Sub message with no signature of its own: anybody who learns the endpoint can post one. What proves it is the OIDC token Pub/Sub puts in the Authorization header, signed by Google, naming the service account the subscription was configured with and the audience it was configured for.

So an endpoint that reads the message and ignores that header is an endpoint where anybody can grant themselves a subscription. Four things are checked, and each has a forged token in the tests:

  • the signature, against Google's published keys, fetched once and held;
  • the issuer, which is Google rather than somebody who said so;
  • the audience, or a token minted for another service is accepted here;
  • the service account, or any Google customer's token is accepted here.

pushAccount is therefore not optional: without it there is nothing to check a token against, and this package refuses to verify rather than verifying nothing.

What it maps

| Play | kind | | --- | --- | | 4 subscription purchased, 1 one-off purchased | purchased | | 2 renewed | renewed | | 3, 20 | cancelled | | 5, 6 | billing_failed | | 1, 7, 12 recovered | recovered | | 10, 11 paused | paused | | 13 expired | expired | | any voided purchase | refunded | | Google's own test notification | other |

A Play notification never says who bought it. event.actor is empty on purpose: the answer comes from asking the API with the purchase token, which is why the reference arrives with the entitlement rather than the event.

Google's "canceled" means "will not renew", not "over". A cancelled subscription is reported as active with renewing: false until its expiry, because treating it as expired cuts a paying customer off early.

Verifying

yarn verify   # ts:check, lint, build, exports:check, test

Google is never reached: the suite signs its own push tokens with keys it generates, stands in for Google's published certificates, and forges every claim worth forging.