Rtdn logo

Rtdn

Organization
RevenueCat
rtdn

Use this skill when setting up Real Time Developer Notifications (RTDN) for Google Play. Covers the full pipeline from Cloud Pub/Sub configuration through message parsing and processing all 18 subscription notification types (codes 1 to 13, 17 to 20, and 22).

Overview

PublisherRevenueCat
Repositoryplay-billing-skills
Skill namertdn
Stars
58
Forks
0
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by RevenueCat on GitHub. Read the source before you install it.

Installation

Install the Rtdn AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/RevenueCat/play-billing-skills/tree/main/google-play/rtdn
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/RevenueCat/play-billing-skills.git /tmp/play-billing-skills
mkdir -p .claude/skills
cp -r /tmp/play-billing-skills/google-play/rtdn .claude/skills/rtdn
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Rtdn in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Rtdn on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Rtdn is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Real Time Developer Notifications (RTDN)

RTDN pushes subscription and purchase state changes from Google Play to your backend within seconds. Use it as the primary signal for entitlement updates; polling the Play Developer API on a schedule is a fallback, not a substitute.

Phase 1: Understand

Pin down the following before you write code:

  • What triggers a notification. Google Play publishes a message to a Cloud Pub/Sub topic you own every time a subscription changes state, a purchase is voided, or (if enabled in the RTDN settings) a one time product is purchased or a pending one is canceled.
  • What the notification contains. A DeveloperNotification JSON object with packageName, eventTimeMillis, and exactly one of subscriptionNotification, oneTimeProductNotification, voidedPurchaseNotification, pendingRefundReviewNotification, or testNotification. Subscription, one time product, and voided purchase notifications carry a purchaseToken (a pending refund review carries orderId and pendingRefundToken instead), not the full purchase state.
  • Who owns what. You own the Pub/Sub topic and subscription in your own Google Cloud project. Google Play only has publish rights.
  • Delivery semantics. Cloud Pub/Sub guarantees at least once delivery and does not guarantee ordering. Duplicates and out of order messages are normal.
  • The golden rule. RTDNs are signals, not sources of truth. For every notification, call purchases.subscriptionsv2.get or purchases.products.get and update entitlements from the API response, not the notification type.

Decide push vs pull:

StrategyPick when
Push subscriptionYou have a public HTTPS endpoint and want a webhook style integration with retries handled by Pub/Sub
Pull subscriptionYour backend has no public URL, needs batch processing, or wants fine grained control over consumption rate

Push is the simpler default. You can swap strategies later without changing the Play Console configuration.

Phase 2: Plan

Write the plan before provisioning anything.

Infrastructure checklist

  • One Pub/Sub topic (example: play-billing-notifications). One topic handles subscriptions, one time products, and voided purchases.
  • One subscription attached to the topic with an appropriate ack-deadline (60s is a reasonable starting point).
  • IAM binding granting roles/pubsub.publisher to google-play-developer-notifications@system.gserviceaccount.com on the topic.
  • Play Console Monetization setup pointing at projects/YOUR_PROJECT_ID/topics/play-billing-notifications.
  • A test notification fired from the Play Console to confirm end to end wiring.

Ack semantics

Your webhook must return 2xx within the ack deadline to acknowledge the message. Any other response or a timeout causes Pub/Sub to retry with exponential backoff. Plan accordingly:

  • Ack as soon as you have durably captured the message (message ID plus payload). Do the API call and database updates inside the same request only if they fit well inside the ack deadline. Otherwise ack fast and process asynchronously from a queue you own.
  • Never ack before you have persisted enough to replay the work. A lost message after ack means a missed state change.
  • Any status other than 102, 200, 201, 202, or 204 (4xx included) is a negative acknowledgement, and Pub/Sub redelivers the message. Return 5xx for transient failures. For a message you can never process, save it to your own error store and return 200, or configure a dead letter topic with a maximum delivery attempts limit.

Idempotency plan

Because duplicates and out of order deliveries are guaranteed, every handler must be idempotent.

  • Deduplicate on Pub/Sub messageId. Persist processed message IDs with a TTL of 24 to 48 hours. Skip any replay you have already seen, then ack.
  • Do not mutate state from the notification type alone. Always call the Developer API for the current state. Two out of order notifications produce two API calls that both return the same current state, so your database converges to the correct value.
  • Track eventTimeMillis per purchaseToken. Record the highest event time you have seen so you can detect stale events if you need to branch logic, but still call the API on every notification.
  • Make database writes idempotent. Use upserts keyed on purchaseToken rather than inserts. Revoking access must be safe to run twice; so must extending expiry.

Monitoring

  • Dashboard the count of unacked messages and the oldest unacked age on the subscription.
  • Alert on sustained non 2xx responses from the webhook.
  • Count notifications by type as a metric so you can spot anomalies (for example, a spike in SUBSCRIPTION_ON_HOLD).

Phase 3: Execute

Step 1: Create the topic

bash
gcloud pubsub topics create play-billing-notifications

Step 2: Grant publish rights to Google Play

bash
gcloud pubsub topics add-iam-policy-binding \
  play-billing-notifications \
  --member=serviceAccount:google-play-developer-notifications@system.gserviceaccount.com \
  --role=roles/pubsub.publisher

Missing this binding is the most common reason a freshly configured RTDN pipeline delivers no messages.

Step 3: Create a push subscription

bash
gcloud pubsub subscriptions create play-billing-sub \
  --topic=play-billing-notifications \
  --push-endpoint=https://api.yourapp.com/billing/rtdn \
  --ack-deadline=60

For pull, omit --push-endpoint and run a subscriber loop in your backend.

Step 4: Enable RTDN in Play Console

In the Play Console, open Monetization setup, find Real time developer notifications, check Enable real-time notifications, enter projects/YOUR_PROJECT_ID/topics/play-billing-notifications, click Send Test Message, choose Get all notifications for subscriptions and one-time products under notification types (the other option omits one time product events), and click Save changes. A push subscriber should receive the test within seconds.

Step 5: Write the webhook endpoint

The push body wraps the notification in a Pub/Sub envelope. The data field is a base64 encoded JSON string.

json
{
  "message": {
    "data": "eyJ2ZXJzaW9uIjoiMS4wIiwi...",
    "messageId": "136969346945",
    "attributes": {}
  },
  "subscription": "projects/myproject/subscriptions/play-billing-sub"
}

Decode the payload:

kotlin
import java.util.Base64

fun decodePubSubData(base64: String): String {
    val bytes = Base64.getDecoder().decode(base64)
    return String(bytes, Charsets.UTF_8)
}

Parse into a DeveloperNotification:

kotlin
data class DeveloperNotification(
    val version: String,
    val packageName: String,
    val eventTimeMillis: Long,
    val subscriptionNotification: SubscriptionNotification? = null,
    val oneTimeProductNotification: OneTimeProductNotification? = null,
    val voidedPurchaseNotification: VoidedPurchaseNotification? = null,
    val pendingRefundReviewNotification: PendingRefundReviewNotification? = null,
    val testNotification: TestNotification? = null
)

Step 6: Dispatch by notification shape

Route by which sub object is present, then by type code.

kotlin
fun route(n: DeveloperNotification) {
    when {
        n.subscriptionNotification != null ->
            handleSubscription(n.subscriptionNotification)
        n.oneTimeProductNotification != null ->
            handleOneTime(n.oneTimeProductNotification)
        n.voidedPurchaseNotification != null ->
            handleVoided(n.voidedPurchaseNotification)
        n.pendingRefundReviewNotification != null ->
            handleRefundReview(n.pendingRefundReviewNotification)
        n.testNotification != null ->
            log("RTDN test received")
        else ->
            log("Unknown RTDN shape: $n")
    }
}

Call orders.reviewrefund (POST applications/{packageName}/orders/{orderId}:reviewrefund) with pendingRefundToken, refundPreference, and sampleContentProvided within 24 hours of a pendingRefundReviewNotification.

Step 7: Deduplicate before processing

kotlin
suspend fun onPubSubMessage(
    messageId: String,
    n: DeveloperNotification
) {
    if (store.hasProcessed(messageId)) return
    route(n)
    store.markProcessed(messageId)
}

Step 8: Always call the API

kotlin
suspend fun handleSubscription(s: SubscriptionNotification) {
    metrics.increment("rtdn.sub.${s.notificationType}")
    val sub = playApi.getSubscriptionV2(
        packageName, s.purchaseToken
    )
    updateEntitlementFromApiState(s.purchaseToken, sub)
}

Apply the same pattern to one time products (purchases.products.get) and voided purchases (revoke the entitlement keyed on the purchase token; for refundType 2 claw back only the refunded quantity, see below).

Notification type reference

Chapter 10 of the handbook documents the 14 subscription types, 2 one time product types, voided purchase, and test notification. The 18 subscription notification types (codes 1 to 13, 17 to 20, and 22; the newer ones are SUBSCRIPTION_ITEMS_CHANGED, SUBSCRIPTION_CANCELLATION_SCHEDULED, SUBSCRIPTION_PRICE_CHANGE_UPDATED, SUBSCRIPTION_PENDING_PURCHASE_CANCELED, and SUBSCRIPTION_PRICE_STEP_UP_CONSENT_UPDATED) are listed in Appendix A of the handbook. See the RTDN chapter on revenuecat.com for in depth coverage of each type; see the rtdn-reference skill or the appendix linked from it for the complete list.

Key subscription types you must handle correctly:

TypeCodeAction
SUBSCRIPTION_RECOVERED1Restore access; payment recovered from hold
SUBSCRIPTION_RENEWED2Extend expiry to new lineItems[].expiryTime
SUBSCRIPTION_CANCELED3Keep access until lineItems[].expiryTime; do not revoke now
SUBSCRIPTION_PURCHASED4Server side safety net; verify the purchase
SUBSCRIPTION_ON_HOLD5Revoke access; keep the subscription record
SUBSCRIPTION_IN_GRACE_PERIOD6Keep access; surface a payment update prompt
SUBSCRIPTION_RESTARTED7User resubscribed before expiry; confirm auto renewing
SUBSCRIPTION_PRICE_CHANGE_CONFIRMED8Deprecated; handle code 19 instead
SUBSCRIPTION_DEFERRED9Fetch new expiry time and extend access
SUBSCRIPTION_PAUSED10Revoke access until resume
SUBSCRIPTION_PAUSE_SCHEDULE_CHANGED11Sync pause schedule with API state
SUBSCRIPTION_REVOKED12Revoke access immediately
SUBSCRIPTION_EXPIRED13Terminal; revoke if not already
SUBSCRIPTION_PRICE_CHANGE_UPDATED19Re-read lineItems[].autoRenewingPlan.priceChangeDetails
SUBSCRIPTION_PENDING_PURCHASE_CANCELED20Clean up the pending record. If it was a pending upgrade, downgrade, or top up, the old subscription stays active

One time products: ONE_TIME_PRODUCT_PURCHASED (1) and ONE_TIME_PRODUCT_CANCELED (2). Voided purchases: revoke and record for reconciliation; productType marks subscription (1) vs one time (2). For refundType 2 (quantity based partial refund) claw back only the refunded quantity (refundableQuantity from purchases.productsv2 holds what remains).

Phase 4: Verify

  • Fire a test notification from Play Console and confirm the webhook logs a testNotification payload.
  • Run an end to end sandbox purchase, cancel it, and verify you receive SUBSCRIPTION_PURCHASED then SUBSCRIPTION_CANCELED with matching purchase tokens.
  • Replay the same Pub/Sub messageId twice and confirm your handler runs the business logic exactly once.
  • Shuffle two notifications for the same token and confirm the final database state matches the API response rather than the order of delivery.
  • Temporarily return 500 from the webhook and confirm Pub/Sub retries and eventually delivers after you return to 200.
  • Check the Pub/Sub subscription metrics for oldest unacked age; it should stay close to zero in steady state.
  • Run a periodic reconciliation job that polls a sample of subscriptions against the API. If it finds drift, your RTDN pipeline has gaps to investigate.

References

Frequently asked questions

What does the Rtdn AI skill do?

Use this skill when setting up Real Time Developer Notifications (RTDN) for Google Play. Covers the full pipeline from Cloud Pub/Sub configuration through message parsing and processing all 18 subscription notification types (codes 1 to 13, 17 to 20, and 22).

Why use Rtdn on TypingMind?

Because you install it once and use it with any model. Rtdn is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Rtdn in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/RevenueCat/play-billing-skills/tree/main/google-play/rtdn. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Rtdn?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Rtdn?

As many as you like. As long as a model supports skills, you can use Rtdn with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Rtdn AI skill free?

Yes. It is published on GitHub by RevenueCat under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇