Subscription packages
A package is the thing you sell: a name, a price, a billing interval and the entitlement handle your game checks. You create and manage packages in your dashboard, and your server keeps subscribing members exactly as it does today. Nothing on this page changes the call your server makes. What changes is that a package now exists before anyone has joined it, so you can price it, rename it, close it and reopen it without touching a subscriber.
Where packages live, and why not in the SDK
Package management is authenticated by your dashboard sign-in, not by X-Game-Secret-Key. That is deliberate: a package spans a title and is a commercial decision, not a per-request action, and your game secret lives on your server where a mis-scoped key would let any integration reprice what your members pay. So the server SDKs do not wrap these endpoints, and you will not find a packages namespace in them. Use the dashboard.
Your subscribe call is unaffected and still authenticates with your game secret. See the card road.
1. Your item_id is the link, and a package owns it
You already send an item_id when your server subscribes someone. It is your own entitlement handle, opaque to Invo, and it comes back on every lifecycle webhook so your code can decide what the subscription grants. Packages do not replace it and Invo does not mint it: you supply it, and a package is the record that describes it.
One live package per item_id, per title
This is not a preference, it follows from how subscriptions are protected. A member can hold at most one live subscription per title, per player, per item_id, which is one of several independent guards against a renewal charging twice. Two live packages sharing an item_id would therefore be mutually exclusive for a member: subscribing to the second would be refused while the first is live.
So creating a second package on an item_id that a live package already uses is refused with ITEM_ID_IN_USE, and the refusal names the package that holds it. Two different titles using the same handle is fine; the rule is per title.
2. A new package adopts the members already on its item
If you have been selling a subscription for a year and you create the package that describes it, the package reports those members immediately. There is no linking step, no backfill to request, and nothing for you to run. Members are counted by title and item_id, which is what the package owns, so a package created today over four hundred existing members shows four hundred.
Nothing about those subscriptions changes when the package appears. They keep their own price, their own interval and their own renewal date, because each subscription carries all three itself.
3. Two subscriber counts, and they are different numbers
A package reports two subscriber figures, and confusing them is the easiest way to read a healthy package as an empty one. Each figure carries the basis it was measured on, so you never have to infer which you are looking at.
| Figure | Counts | Use it for |
|---|---|---|
subscriber_countbasis active | Members being charged: active, and not already set to cancel at the end of their period. | Revenue. It is the same basis as the monthly figure beside it, so the two add up. |
live_subscriber_countbasis live | Every member the billing system still holds: in trial, active, past due, awaiting authentication, and those cancelling at period end. | Anything phrased as "is anyone on this". These are real subscribers. |
The trap, stated plainly
A package whose members are all in trial reports subscriber_count: 0 and live_subscriber_count: 400. Both numbers are correct. Nobody is being charged today and four hundred people are subscribed. If you build a sentence like "nobody is subscribed" out of the first number you will tell yourself something false at the worst possible moment, which is why the second number exists and why both carry a basis. The live figure is always the larger of the two.
4. Changing a price is a decision you state, not a default
When you change a package price you must say who it applies to. There is no default, and omitting the choice is refused rather than guessed, because a default here is how thousands of members quietly start paying a different amount.
New subscribers only (available now)
The package price changes for people who join from now on. Everyone already subscribed keeps the price they are on, indefinitely. Nothing about a current member changes.
Existing subscribers too (not available yet)
Current members move as well, each at their own next renewal, never mid-period and never prorated. Until this ships the request is refused with a named code rather than half-applied, so you can build the choice into your process now and know that only one of the two paths can run.
Two consequences worth knowing:
- Renaming a package is not a price change and never asks who it applies to. Neither is re-describing it. Only a price that actually differs from the stored one counts, so re-saving a form with the price untouched changes nothing and is not refused.
- A closed package cannot be repriced. Its members keep the price they are on, and there are no new subscribers to price. Reopen it first if you want to change it.
5. Closing a package cancels nobody
Closing a package, which the API calls retiring, stops new subscriptions and does nothing whatever to existing ones. Every current member continues, renews and is billed exactly as before, at the price they were on. This is structural rather than a promise: a subscription carries its own price, interval and item, and the billing system does not read the package.
For the same reason a package with members can be closed but never deleted. Removing a catalogue entry must not be a way to end somebody's billing arrangement, so it is not one.
Closing is idempotent. Closing an already closed package succeeds and reports that it was already closed, so a retry after a dropped connection is not a failure. The response also tells you how many members were left untouched, which is usually the number you actually wanted to see before you pressed the button.
6. Reopening, and the one thing that can block it
A closed package can be reopened. It returns to taking new subscribers at the price it already carried, and it can be repriced again. Reopening grants nobody anything, because closing took nothing away.
If you replaced it, you cannot reopen it
Closing a package frees its item_id for a new package. If you use it, the old package can no longer be reopened, because that would leave two live packages over one handle and no member could hold both. Reopening is then refused with ITEM_ID_IN_USE, naming the package that now holds the handle. Close the replacement first if you want the original back.
There is also a guard in the other direction. While a closed package still has live members, creating a new package on its item_id is refused with ITEM_ID_HAS_SUBSCRIBERS, because those members are counted by handle and the two packages would report each other's people. Reopen the original, or use a different handle.
7. What none of this changes
Your integration is untouched. Your server subscribes members the same way, with the same credential, the same fields and the same item_id. Webhooks carry the same payloads. Entitlement still comes from the renewal event, not from the package.
Packages change your catalogue, not your wire format.
Your server still: subscribe with X-Game-Secret-Key, passing item_id
Your game still: grants on the renewal event, keyed on item_id
Your members still: keep their own price until you deliberately move them
New, and dashboard only:
create a package before anyone has joined it
see who is on it, on two clearly labelled bases
change its price for new members
close it without cancelling anyone, and reopen itIf a package price and a member's price disagree, the member's price is the one they pay. That is not a bug to report: it is what keeps a price change from reaching into a period somebody has already been billed for.