UnifiWallet Payments

Subscriptions

A signed, capped, revocable permission instead of a stored card — and the buyer holds the off switch.

A recurring charge with no stored card and no custody. The buyer signs one permission — a ceiling, a period, your address and the fee — and we charge against it each cycle. They can revoke it in their own wallet at any time, without asking us.

Two ways to collect

<code>auto_charge</code><code>payment_link</code>
Buyer signsOnce, a spend permissionNothing
Each cycleWe pull against itWe email a checkout
NeedsAn ERC-20 and a supported chainNothing on chain
Fails whenWallet empty, permission revokedThe buyer does not pay

Every subscription route, with parameters and samples, is on the API reference.

Creating one charges nothing

A new subscription is incomplete until the buyer acts. Treat that literally: a merchant who reads active off a subscription nobody has agreed to will ship for nothing.

const sub = await call('POST', '/v1/subscriptions', {
  amount: '19.99',
  currency: 'USD',
  interval: 'month',
  collection_method: 'auto_charge',
  description: 'Pro plan',
  customer: {
    reference: 'cus_88213',
    email: 'buyer@example.com',
    wallet_address: '0x4b2c...',
  },
});

// Nothing is billable yet. Hand this to the buyer's wallet to sign.
console.log(sub.authorization.typed_data);
console.log(sub.authorization.human_summary);
// { most_per_period: '19.99', asset: 'USDC', period_days: 28, ... }

What the buyer signs

Everything that decides where money goes is inside the signature: the recipient, the fee, the ceiling, the period. Two consequences follow, and both are the point.

  • We cannot choose where it goes. A permission that let us name the recipient at charge time would be custody with extra steps. Binding your address into the signature means the worst a compromised spender can do is pay you early.
  • We cannot raise our own fee. The split is fixed at signing, and the contract refuses more than its hard ceiling however the request was built.
// The buyer signs in their wallet. No gas, nothing on chain yet.
const signature = await wallet.signTypedData(
  sub.authorization.typed_data.domain,
  sub.authorization.typed_data.types,
  sub.authorization.typed_data.message,
);

const active = await call('POST', `/v1/subscriptions/${sub.id}/authorize`, {
  permission: sub.authorization.permission,
  signature,
});

// Now it is billable.
console.log(active.status); // 'active'

The ceiling is real

Periods do not accumulate. A buyer who authorises 19.99 per period can never be charged 240 in December because eleven cycles were skipped — each period starts from zero.

The on-chain window is the shortest the interval can be — 28 days for a month — and the human_summary states that window rather than the word “monthly”. The exact one-charge-per-cycle guarantee is enforced here; the chain is the backstop for the total.

The buyer can cancel without asking you

Creating a subscription returns a manage_url once. It is the buyer's own link to their subscription: it shows what they are paying and stops it, needs no key, no signature and no account, and is addressed by an unguessable token exactly like a checkout link. We include it in every subscription email we send the payer.

Put it in your own emails and on your own account page too. The shortest route out of a subscription should be inside your product rather than only in a message from a payments company your customer has never heard of. It costs you nothing: a customer who cannot find the cancel button does not keep paying, they charge back, complain, or tell people.

Cancelling from that link defaults to the end of the period they have already paid for — which is what a person means by cancelling — and immediately ends it at once. Either way you are told: subscription.cancel_scheduled or subscription.canceled, both carrying canceled_by: "buyer". Act on it. We stop charging; only you can stop providing the service, and continuing to serve someone you are no longer billing is your bill to pay.

There is no confirmation step on that page, no retention offer, and no required reason. That is deliberate. A cancel flow that argues with the person using it is a dark pattern, it is increasingly illegal, and it would contradict the whole product: we are not going to build one, and this endpoint means nobody can put one in front of it.

Two halves, and only one of them is a promise.

Cancelling — by you or by the buyer — stops us charging, and we keep to that. The permission itself is a separate thing: it sits on chain until it expires, and only the buyer's own wallet can withdraw it. That half needs neither us nor you and cannot be quietly walked back, which is exactly what makes it theirs.

Both cancel responses carry the revoke call, and the buyer's page links to it. Point your customers at it rather than reassuring them: a verifiable action beats a promise from either of us.

When a charge fails

Retries run at 12 hours, 24 hours, 72 hours and 7 days, then the subscription is cancelled. The spacing is deliberate: the overwhelming reason a crypto renewal fails is a wallet that is briefly empty, and that is fixed on human timescales. Retrying hourly burns gas and teaches merchants to ignore the alerts.

Reasons are distinguished, because they need different responses. A revoked permission means the subscription is over; an empty wallet means try again. Telling somebody to top up an account they already closed is how a provider gets ignored.

Collecting by link

A payment_link subscription signs nothing and pulls nothing. Each cycle we create a real payment and email the buyer its checkout, and subscription.cycle_due carries the same link if you would rather send it yourself.

The cycle is marked paid when its payment settles — you get subscription.cycle_paid, and the first one moves the subscription from incomplete to active. A cycle whose payment expires unpaid becomes past_due, and three unpaid cycles in a row stop the subscription: there is no point emailing invoices to somebody who has stopped paying.

Cycles

Each billing period is its own record, created when it becomes due whether or not it ever produces a payment. That is what makes “we tried four times over eight days and it never worked” a question with an answer, rather than a gap between two payments.

One cycle per period, keyed by the period itself. An unpaid cycle never blocks the next one from opening — which it did once, and a link subscription then billed exactly once and went quiet.

GET /v1/subscriptions/{id}/cycles returns them with attempt counts and failure reasons.

Calendar dates

Monthly billing follows the calendar and anchors on the day of the month, clamped. Subscribe on the 31st and you are billed on the 30th in November and the 28th in February — then on the 31st again in March, because the anchor is remembered rather than lost to the shortest month it passed through.