VoidhashDocs
DevelopersHelp centerGuidesAPI reference
voidhash.comOpen Studio
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Groups
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Theme

Subscription lifecycle

How purchases, renewals, cancellations, refunds and revocations change a person's access and which webhooks fire.

A subscription moves from purchase through renewals to its end. Voidhash learns each change from the store or payment source, updates the person's entitlements, and sends a webhook when the change matters to your backend.

Gate access on perks, not on subscription status. A perk stays active in every state that still entitles the person, so one check covers trials, grace periods and pending cancellations.

How Voidhash learns about changes

ProviderSource of changes
App StoreThe SDK reports each transaction; App Store Server Notifications report later changes.
Google PlayThe SDK reports each purchase; Real-time developer notifications (RTDN) report later changes.
StripeStripe events such as invoice.paid, customer.subscription.updated, customer.subscription.deleted and charge.refunded, each read back from Stripe.
Payment sourceYour provider endpoint reports transactions and subscription states. These events carry verified: false.

Voidhash processes each store event once: a repeat sends no second webhook, and a late event can't move a subscription back to an older state. An event for an unmapped store product is applied once you map it, if you do so within a year. See Unmapped purchases.

States

PurchaseActiveAuto-renew offBilling issueEndedrenewrefund, revoke, cancel now

A refund, revocation or immediate cancellation ends a subscription at once (dashed).

Statuses

Voidhash stores two statuses, which webhooks report in status. A subscription is canceled once it expired, was revoked, or was refunded on the App Store. Until then it is active, including trials, grace periods and auto-renew off, which sets cancelAtPeriodEnd and canceledAt instead.

The SDKs read a detailed status from what the store reports. Each subscription in the person's subscriptions carries it as status, with periodType (trial, intro or regular) and willRenew:

SituationAPI and webhook statusSDK statusPeople label
Free trialactive, isTrial: truetrialingActive
Paid, including an offer's discounted periodactiveactiveActive
Auto-renew off, period not overactive, cancelAtPeriodEnd: trueactive or trialing, willRenew: falseCancels at period end
Renewal failed, in a grace periodactivein_grace_periodGrace period
Renewal failed, the store retries without accessactivein_billing_retryExpired
Google Play account holdactiveon_holdExpired
Paused on Google PlayactivepausedExpired
End date passed by over 24 hours, store hasn't reported the endactiveexpiredExpired
Ended: expired or canceled at oncecanceledexpiredExpired (Canceled while its end date is still ahead)
Access withdrawn: revoked, or refunded on the App StorecanceledrevokedExpired

subscriptions.all lists every subscription that is neither expired nor revoked, best first: active, then trialing, in_grace_period, in_billing_retry, on_hold and paused, the latest to expire first. subscriptions.current is the first of them, or null. A subscription whose perks still apply, in a grace period or within the 24-hour buffer below, stays listed. subscriptions.history lists every subscription, ended ones included, with the same status. Use these for account screens, such as "Renews on" or "Update your payment method"; gate features on perks.

Each subscription also reports what changes next: renewsToProductSlug for a downgrade or crossgrade the store applies at renewal, pendingPrice for a price change with whether the customer agreed to it (consent), resumesAt for a pause, billingIssueDetectedAt and gracePeriodExpiresAt for a failed renewal, and refundedAt and revokedAt.

When perks end

Perks stay active until the later of expiry and the end of any grace period, plus 24 hours for late renewal notifications. A revoked or ended subscription loses its perks at once. A one-time purchase keeps them until it is refunded or revoked; a partial refund doesn't remove them.

Grace periods and billing retry

When a renewal payment fails, the store retries it. If you turned on a grace period (Billing Grace Period in App Store Connect, or a grace period in Google Play Console), the person keeps access while the store retries: Voidhash keeps their perks until the grace period ends, plus the 24 hours above. The subscription stays active and no webhook fires. Without a grace period, perks end at the expiry date plus 24 hours.

If the payment recovers, the subscription renews (subscription.renewed) and the perks return. When a Google Play grace period ends unpaid, Google puts the subscription on account hold: its perks end at once, and it reads as on_hold until a payment recovers it or Google ends it. Payment sources report past_due, which starts billing retry without a grace period.

Lifecycle events

ChangeAccessWebhook
PurchasePerks grantedsubscription.created
Free trial startsPerks granted; isTrial: truesubscription.created
Trial converts or the subscription renewsPerks extended to the new expirysubscription.renewed
Auto-renew turned offPerks kept until expirysubscription.cancelled
Auto-renew turned back onNo changeNone
Grace period or billing retryPerks kept, see Grace periodsNone
Google Play account hold or pausePerks end until a payment resumes itNone
Payment recoveredPerks extendedsubscription.renewed
ExpirationPerks endsubscription.expired with expirationReason: "ended"
Revocation (Family Sharing removed, Google revoke)Perks end at oncesubscription.expired with expirationReason: "revoked"
RefundDepends on the store, see belowpurchase.refunded
Product changeNew product's perksDepends on the store, see below
One-time or consumable purchasePerks grantedpurchase.completed
One-time purchase refunded or revokedPerks endpurchase.refunded or purchase.revoked

subscription.cancelled means auto-renew is off, not that access ended; end access on subscription.expired. A subscription Stripe ends immediately sends only subscription.expired. See Webhook events for payloads.

Store differences

Trials

  • App Store: only a free-trial introductory offer counts as a trial. Pay-up-front and pay-as-you-go offers are paid periods.
  • Google Play: only a free-trial phase is a trial; an introductory-price phase is a paid period with periodType: "intro". An offer whose phase Google doesn't report is not a trial either, and its price is recorded once Google reports the order rather than guessed from the base plan.
  • Stripe: a period that starts before trial_end is a trial.

Refunds

  • App Store: refunding the current period ends the subscription and its perks at the refund date and sends subscription.expired; the SDKs read it as revoked. Refunding a past period changes nothing.
  • Google Play and Stripe: a refund records the money and sets the subscription's refundedAt; access ends when the store expires or revokes the subscription. A lost Stripe dispute is a refund with refundReason: "chargeback_lost".
  • Payment sources: a reported refund doesn't end the subscription.

A reversed App Store refund restores the period. No webhook fires for it.

Product changes

StoreUpgradeDowngrade
App StoreImmediate; same subscription; subscription.renewedWaits for the next renewal; subscription.renewed then
Google PlayA new subscription replaces the old: subscription.created for it, subscription.expired for the oldDeferred: the new subscription keeps the old product, with renewsToProductSlug naming the new one, and switches at its renewal (subscription.renewed). The new product's perks start then
Stripesubscription.product_changed at the switch, then subscription.renewed when it is chargedApplied when the scheduled change takes effect

Other differences

  • Resubscribing after expiry continues the same subscription on the App Store (subscription.renewed). Google Play and Stripe start a new one (subscription.created).
  • Pausing on Google Play ends the perks while the subscription is paused (paused, with resumesAt); the payment that resumes it renews it.
  • Non-renewing subscriptions on the App Store mapped to a subscription product end one billing duration after purchase. Mapped to a non-consumable, they are a one-time purchase.
  • Payment sources report trialing, active, past_due, canceled or expired. past_due starts billing retry without a grace period.

Simulate the lifecycle

In the test environment, renew, expire, revoke, refund or start a 3-day grace period on a simulated purchase with the MCP tool simulate_sandbox_event, POST /api/v1/projects/{projectId}/sandbox/actions with a secret key, or the CLI's voidhash sandbox renew, expire, grace-period, refund or revoke. Access changes at once, and webhooks follow. See Simulate renewals, expiry and refunds.

Next steps

  • Webhook events: every payload field.
  • Check access from your backend with Node.js, PHP, Go, Rust or over HTTP.
  • Test purchases (Help center): store sandboxes and simulated purchases.
  • Products and perks: what each purchase grants.

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousOffersNextPeople and identity
© 2026 Voidhash s.r.o.
voidhash.comPricingTermsPrivacyDiscord

On this page

How Voidhash learns about changesStatesStatusesWhen perks endGrace periods and billing retryLifecycle eventsStore differencesTrialsRefundsProduct changesOther differencesSimulate the lifecycleNext steps