Subscriptions
How recurring billing works between agents and service providers on-chain.
A Subscription is an on-chain account that links an agent wallet to a Plan. It authorizes the service provider to pull a specified amount of USDC from the agent’s wallet at each billing cycle, without requiring the agent to sign each transaction.
Subscription structure
interface Subscription {
id: PublicKey; // On-chain subscription address
subscriber: PublicKey; // Agent wallet
plan: PublicKey; // Plan being subscribed to
status: SubscriptionStatus; // TRIAL | ACTIVE | PAUSED | CANCELLED
startedAt: number; // Unix timestamp
trialEndsAt?: number; // Unix timestamp, if applicable
nextBillingAt: number; // Unix timestamp of next collection
cycleCount: number; // Number of completed billing cycles
authorizedAmount: number; // Max pull per cycle (in token base units)
}
Lifecycle
subscribe()
|
v
TRIAL (if trialPeriodDays > 0)
|
| trial period ends
v
ACTIVE <---+
| |
| | billing cycle completes, collection succeeds
| |
| collection fails (insufficient funds)
v
PAUSED -->--+ (after retry window, if still failing)
|
| agent cancels or provider cancels
v
CANCELLED
TRIAL
If the plan includes a trial period, the subscription starts in TRIAL status. No funds are collected during the trial. The first billing cycle begins when the trial ends.
ACTIVE
An ACTIVE subscription bills automatically at each renewal date. The service provider’s system calls recur.collect() (or Recur’s automation calls it) to pull funds from the subscriber’s wallet. The on-chain Subscription Authority validates the authorization before executing the transfer.
PAUSED
If collection fails — for example because the agent wallet has insufficient USDC — the subscription enters PAUSED status. Recur retries collection over a configurable window (default: 3 attempts over 7 days). If all retries fail, the subscription remains paused and a subscription.payment_failed webhook is emitted.
Agents or providers can manually unpause a subscription after funds are topped up.
CANCELLED
A cancelled subscription stops billing immediately. The cancellation is written on-chain. Any prepaid time through the current billing cycle remains accessible (providers are responsible for enforcing grace periods, if any).
How automatic billing works
Solana smart contracts cannot pull funds from a wallet without explicit authorization. Recur solves this through Solana’s native Subscriptions and Allowances Program.
When an agent calls agent.subscribe(), the SDK creates a Subscription account on-chain and delegates a Subscription Authority to the service provider for the specific (wallet, token, authorized_amount) combination. The authority is scoped — it only allows the provider to pull up to the authorized amount per billing cycle.
At each renewal, the provider (or Recur’s automation) calls collect() on the Solana program. The program checks the Subscription account, verifies the authorization is still valid, and executes the token transfer. The agent never needs to sign individual billing transactions.
Creating a subscription
From the agent side:
const sub = await agent.subscribe({
planId: "7xKp...plan_address",
});
console.log(sub.id); // on-chain subscription address
console.log(sub.status); // "TRIAL" or "ACTIVE"
console.log(sub.nextBillingAt); // Unix timestamp
Verifying a subscription
From the provider side, to check whether an incoming request has an active subscription:
const isValid = await recur.verifySubscription({
subscriber: requestingWallet,
plan: plan.id,
});
if (!isValid) {
return res.status(403).json({ error: "No active subscription" });
}
The payment gate middleware handles this automatically for most use cases.
Cancellation
// Agent cancels their subscription
await agent.cancelSubscription({ subscriptionId: sub.id });
// Provider cancels (e.g. for policy violations)
await recur.cancelSubscription({ subscriptionId: sub.id });
Cancellations are on-chain operations. They are final and publicly verifiable.
Viewing active subscriptions
// From the agent side — all active subscriptions for this wallet
const subscriptions = await agent.listSubscriptions();
// From the provider side — all subscribers on a plan
const subscribers = await recur.listSubscribers({ plan: plan.id });
You can also view subscriptions in the Recur Dashboard alongside MRR trends, churn rate, and per-subscriber transaction history.