Subscriptions
Subscriptions let you set up recurring billing for your customers. Each billing period, Plirin generates an invoice, sends it to the customer, and tracks whether it gets paid. The customer pays that invoice the same way they would pay any other Plirin invoice.
A subscription collects one of two ways. Send invoice is the default and always available: Plirin mints the period’s invoice and sends it, and the customer pays it like any other invoice. Automatic collection additionally pulls the charge on chain, against a mandate the customer authorized once, so they do not have to act on each invoice. See Automatic collection below for where it is available and what the customer is agreeing to.
Creating a subscription
Section titled “Creating a subscription”- Navigate to Subscriptions and click New subscription plan
- Select a recurring price (only recurring price types are shown)
- Optionally attach a customer
- Optionally set a trial period, in days
This issues a checkout link, not a live subscription: the subscription is created Incomplete, and nothing bills until the customer opens the link and connects a wallet.
- Leave the customer field blank for an open link: whoever connects a wallet first becomes the subscriber.
- Attach a customer to restrict the link to them: only their connected wallet can complete it, and a different wallet is refused.
Plirin does not send the link to anyone. There is no email dispatch on this path. You deliver the checkout link to the customer yourself, the same way you would share a payment link.
Once the customer completes the link, their wallet resolves to a customer record (or is matched against the one you preselected), and the subscription starts its first period: Trialing if a trial applies, or Active with its first invoice minted immediately if it does not. The checkout form also asks for an email, optionally: give one and Plirin emails that invoice (and every one after it, for a customer who does not already have an email on file); leave it blank and the confirmation screen shows the invoice’s hosted payment link directly, so the customer can still pay right away.
A checkout link left incomplete for 30 days expires on its own into Incomplete Expired; nobody can complete it after that.
The API and SDKs can also start a subscription immediately, without a checkout link, by passing a customer directly to createSubscription (GraphQL) or POST /api/v1/subscriptions (REST). That path always starts Trialing or Active and never touches Incomplete at all; the dashboard’s own create flow only uses the checkout-link path above.
How subscriptions work
Section titled “How subscriptions work”Once active, a subscription:
- Generates an invoice at the start of each billing period and sends it to the customer
- Advances to the next period as soon as that invoice is sent, not once it is paid: non-payment does not roll the period back
- Is not billed again while the invoice from an earlier period is still unpaid past its due date, until that invoice is settled
- Retries a failed billing attempt on a fixed schedule (see Managing Subscriptions)
- Continues until cancelled, or until it is put
on_holdafter repeated failed billing attempts
A subscription with an active mandate follows the same path, with one extra step: the period’s invoice is minted first, then Plirin attempts to collect it on chain. A successful collection settles the invoice before it is ever sent, so nothing lands in the customer’s inbox. Anything that stops the collection falls back to sending that same invoice.
Automatic collection
Section titled “Automatic collection”Automatic collection needs a mandate: a one-time on-chain authorization the customer signs from the subscription’s checkout link. On Solana it is an SPL approve that names Plirin’s collection key as the delegate on the customer’s own token account, with a ceiling of one period’s cap multiplied by the number of periods authorized (12 by default).
That approve also carries a one-off reference key, unique to the authorization being signed and generated when the checkout page asks for it. It changes nothing about what the approve does; it is what lets Plirin prove the transaction answers that authorization rather than some other one on the same wallet, and an approve that does not carry the right reference is refused. Each approve backs exactly one mandate.
You cannot set automatic when creating a subscription. A brand new subscription has no mandate and no chain yet, so both the API and the SDKs reject it there; create the subscription as send_invoice, and it becomes automatic once the customer authorizes a mandate.
Where it is available
Section titled “Where it is available”| Chain | Automatic collection |
|---|---|
| Solana (mainnet and devnet) | Available today |
| Base, Polygon | Not yet. Both are waiting on their collection adapter |
| Ethereum mainnet | Never. See below |
Ethereum mainnet does not support automatic collection and will not. Plirin pays the gas on every renewal out of its own balance, and on Ethereum mainnet that gas can exceed the platform fee outright: at the Growth tier’s 1.5%, a $20/month plan earns $0.30 in platform fee, an amount ordinary Ethereum mainnet gas can exceed. Subscriptions on Ethereum mainnet are send_invoice only.
What the customer is agreeing to
Section titled “What the customer is agreeing to”The authorization is capped four ways: one period’s price (not the whole remaining allowance), one charge per period, a fixed pair of recipients (your registered payout wallet and the Plirin fee wallet), and a finite number of periods, after which the customer re-authorizes. The chain enforces the total ceiling; Plirin enforces the rest.
When a period is not collected
Section titled “When a period is not collected”Some conditions stop a single period’s collection without failing the subscription. Plirin sends that period’s invoice instead, exactly as a send_invoice subscription would, and nothing is dunned:
- The period’s real charge exceeds the mandate’s per-period cap. This is what a
subscription.mandate_insufficientwebhook reports. - Your receiving wallet changed since the mandate’s last confirmed collection. Same webhook, different reason.
- The mandate was revoked or its authorized periods ran out.
subscription.mandate_expiring fires after each successful collection, on either of two triggers. A mandate with two or fewer periods left fires it so you can prompt the customer to re-authorize before it runs out; on Solana this is routine near the end of every mandate, not an alarm. It also fires when this mandate shares its Solana token account with another active mandate on the same wallet and token, and that sibling’s own charges have drawn the shared on-chain allowance down below what this mandate needs for its very next charge, even while this mandate’s own period count still looks healthy. Do not treat “periods remaining” as the whole story: the second trigger can fire with any number of periods nominally left.
Revoking
Section titled “Revoking”You can stop collecting at any time, from the API or an SDK. Read the honest limits of that in Managing Subscriptions: a merchant-side revocation stops Plirin from charging, but only the customer can remove the on-chain authorization.
Subscription statuses
Section titled “Subscription statuses”| Status | Description |
|---|---|
| Incomplete | A checkout link has been issued but the customer has not yet completed it. No invoice exists yet |
| Incomplete Expired | An Incomplete subscription’s 30-day window to complete the checkout link ran out before a customer connected a wallet |
| Trialing | In a trial period, no invoice generated yet |
| Active | Billing normally on schedule |
| Past Due | The latest invoice is unpaid and a retry is scheduled |
| On Hold | The latest invoice went unpaid through every scheduled retry. Plirin stops retrying automatically at this point; treat this the same as Past Due for the purpose of gating a customer’s access; a customer who later pays the outstanding invoice returns the subscription to Active |
| Paused | Temporarily suspended, no invoices generated |
| Cancelled | Permanently stopped |