status at a time. It is what GET /v1/access and
every webhook carry.status | access | payment_status | What it means |
none | false | none | This customer never subscribed to this plan. Not an error |
pending | false | none | Reserved, waiting for the wallet's transaction to land |
active | true | paid | Paid up. The next charge is at next_charge_at |
unpaid | true, false on Free | late | A charge missed. A retry is coming |
cancelled | true until the end | paid | The customer cancelled. The period already paid runs to access_until |
stopped | false | none | Charging stopped, most often when the retries ran out. stop_reason says why |
ended | false | none | Over. end_reason says why |
failed | false | none | What landed on chain is not what Mesub reserved, so it is not billed. Rare |
superseded | false | none | Replaced by a newer subscription of the same wallet to the same plan |
ended, failed and superseded are final. stopped is not: Mesub charges
nothing more, but the wallet's approval stays open on Solana until the customer
cancels then closes it, and until then they can come back.access, not on status: a cancelled subscription still grants
access for a while, an unpaid one may or may not, and an active one has
none once its plan has ended.expired. It never appears in /v1/access. It
does in GET /v1/subscriptions, when you read a subscription by its id, and
in the subscription.expired webhook. Only a superseded one is left out of
the list.unpaid and payment_status reads late.late_reason says why, as the last failed charge found it. A stopped
subscription keeps it too, for the payment it stopped on. It is null on every
other status, and when Mesub cannot name the cause.late_reason | What happened | What fixes it |
insufficient_balance | The wallet holds less than the price | Adding funds |
approval_revoked | The wallet no longer lets Mesub charge it | Not adding funds |
authority_closed | The wallet closed its authorisation, for good | Nothing |
late_reason before you tell a customer to
top up. Every charge and retry is kept with its own outcome and reason:
read an attempt says what each means.| Plan period | Wait between tries |
| 1 month | 1 day |
| 1 week | 1 day |
| 1 day | 6 hours |
| 1 hour | 15 minutes |
next_retry_at says when the next one is,
next_retry_number and retries_allowed say which one it is, and
access_until is the date of the last retry: if that one fails too, the
subscription turns stopped and access ends.active. On insufficient_balance the
customer has nothing to sign: they only need to add funds.unpaid subscription grants no
access. You can retry by hand, up to 3 times, until retry_deadline: from
the dashboard, or from your own page with
a retry.retry_deadline is two minutes before the missed period ends. Past it, a retry
is refused with retry_deadline_passed. The subscription turns stopped a
few minutes later, once the missed period is over: for instance a deadline at
15:15 stops it around 15:19, after the period ends at 15:17.subscriptions.retry, which the widget shows as Pay now.
Pay a late payment now has the detail.stopped subscription carries stop_reason. It is null on every other
status, and on one that stopped before Mesub recorded why.stop_reason | What happened | What the customer can do |
out_of_retries | Every retry of the missed payment failed | Come back, or cancel then close |
retry_deadline_passed | On Free, the last day to pay the missed payment passed | Come back, or cancel then close |
authority_closed | The wallet closed its authorisation for good. Nobody can charge it | Close it at once (closable is true) |
authority_closed cannot come back: the customer closes it,
then subscribes anew.active, or unpaid
if it was late already, and payment_status stays as it was. Two fields say
it instead, on GET /v1/access and on
the subscription: blocked_since,
the date the block began, and blocked_reason. Both are null on a
subscription that is not blocked.blocked_reason | What happened | What it asks of you |
puller_not_allowed | Mesub's puller was taken off the plan on chain | Add Mesub back to the plan's pullers, with the wallet that owns the plan |
receiver_not_allowed | The receiving wallet is not among the plan's destinations | Choose a receiving wallet that is among them |
receiver_account_missing | It has no account for the plan's token, and nobody else can be paid | Open an account for that token |
receiver_account_frozen | The token account it is paid into is frozen | Have it thawed, or choose another receiving wallet |
receiver_account_needs_memo | That token account requires a memo on every transfer in | Turn the memo requirement off, or choose another receiving wallet |
receiver_account_confidential_only | That token account takes confidential transfers only | Allow plain transfers into it, or choose another receiving wallet |
mint_gone | The plan's token no longer exists | Create a new plan with another token |
mint_paused | The token's issuer paused it | Nothing: Mesub charges again once the issuer lifts the pause |
mint_transfer_hook | The token's issuer made its transfers run a program of theirs | Nothing, if the issuer removes it. Otherwise a new plan in another token |
mesub | Any cause on Mesub's side | Nothing |
mint_paused and mint_transfer_hook are the token issuer's: only the issuer
lifts them. Mesub mails you when your setup or the issuer holds your charges.
mesub covers every other cause, whatever it is.next_charge_at is the next try, or next_retry_at on a subscription that
was late already. The first charge that goes through, or that the chain holds
as paid, lifts the block. Access comes back with it, and the customer has
nothing to sign.access_until is its date:blocked_reason | Access lasts |
mesub | To the end of the period after the one paid for |
| Any other | Past the paid period, as long as the plan's retries give a late payment |
access is never true past access_until on a blocked subscription.blocked_since is the date of the block blocked_reason names: when a cause
on Mesub's side gives way to one of your setup, or back, it starts again.| Field | Before | After cancelling |
status | active | cancelled |
access | true | true until access_until |
cancelled_at | null | When they cancelled |
next_charge_at | a date | null |
access_until, they can resume: one more signature, and the
subscription is active again with its next charge back on schedule.access_until, it turns ended with end_reason: "cancelled". The
customer can then close it to get the rent of its account back.ends_at on
the plan says when, and is null on a plan
with no end.access is false from that
moment on, for a subscription that is active, unpaid, cancelled or
parked alike, and access_until is never later than the plan's end.| Field | What it reads near the plan's end |
access_until | The plan's end, when the paid period or the retries run past it |
next_charge_at | null: a subscription in its last period has no next charge |
next_retry_at | null when the plan ends before the retry |
retry_deadline | On Free, the plan's end when it comes before the deadline |
active or unpaid turns ended
with end_reason: "plan_ended", and subscription.ended is sent. One the
customer had cancelled ends with cancelled, as it would have.status
can still read active or unpaid while access is already false. It never
reads ended with access true. A guard reads access, never status.plan_ended.ended subscription carries one end_reason:end_reason | What happened |
cancelled | The customer cancelled and the paid period ran out |
plan_ended | The plan reached its own end date |
plan_removed | You deleted the plan |
plan_replaced | Another plan now stands at its address |
closed_elsewhere | It was closed on chain, outside Mesub |
closed | The customer closed the subscription through Mesub |
paused is true, the status
stays as it was, nothing is charged, and access runs to the end of the period
already paid. Moving back up a tier puts them back on schedule.stopped one
turns superseded; an ended one stays as it was. A stopped subscription holds no seat, so coming back needs
a free one, like any new subscriber: on a full project it is refused with
seat_cap_reached.comeback_period_rolling, so the terms the
wallet signs name the period that is really charged. Wait for the Retry-After
it carries, then create the subscription again: a transaction built before the
wait is not sent.subscribed_since is the start of the current one, and
first_subscribed_at the start of the very first.