Accept USDC subscriptions in your Solana app
By the end of this page, your app has three things:
- a plan on Solana, say 9 USDC a month, paid to your wallet;
- a Subscribe button that opens the customer's wallet;
- a paid route on your server that answers only for customers who paid.
After that, every renewal is charged on schedule, a failed one is retried on the paid tiers, and your route keeps giving the right answer, without more code from you.
The full code for Express, Next.js and NestJS is in the quickstart. This page explains what each piece does, and what happens after the first payment.
The customer approves once
A subscription on Solana is a permission the customer gives once. They sign the plan's terms: this amount, this often. From then on, the plan can collect up to that amount each period, and never more. The rule is enforced by the Solana Subscriptions program, not by Mesub.
The customer stays in control. They can cancel at any time, from their side, and nothing is charged after the period they are in. Their money stays in their wallet until each payment.
What Mesub adds is everything after the signature: sending each payment when it is due, retrying the ones that fail, and telling your server who has access.
Before you start
- Two wallets, in an app such as Phantom or Solflare. One is yours: it publishes the plan and receives the money. The other plays your customer.
- Some SOL on both. Your wallet pays the plan's rent and the network fee to publish it. The customer's pays the rent of their subscription and of its authority, and the network fee to subscribe.
- A little USDC on the customer's wallet, enough for one payment. A cheap test plan, 1 USDC a day, is enough.
- A server with your own login. Mesub does not log your users in. It asks your server who the user is.
Step 1: publish a plan
In the Mesub dashboard, open Plans, then New plan. Give it a name, a price, a token, a period, and the wallet that receives the money. An end date is optional.
Your organization's wallet signs the plan's creation, so the plan is yours on chain. To make sure only your wallets can ever receive the money, list them as the plan's destinations: they cannot be changed later. Mesub turns its name into a short slug: a plan named Pro becomes pro. Your code uses the slug everywhere.
Step 2: give your server the API key
In Developers, then Keys, create the project's API key. It starts with SUB_ and is shown once. Put it in your server's environment, then install the SDK:
MESUB_API_KEY=SUB_...
npm install @mesub/node
The key stays on your server. The browser never sees it.
Step 3: mount the routes and gate a route
Your server needs two things. The routes are what the button calls to subscribe. The gate is the question you ask before serving anything paid. With Express:
import { Mesub } from "@mesub/node";
import { mesubRoutes } from "@mesub/node/express";
const mesub = new Mesub(); // reads MESUB_API_KEY
// What the button calls. Mount it after your own login.
app.use(
"/api/mesub",
yourLogin,
mesubRoutes({
customer: (req) => (req.user ? { external_id: req.user.id } : null),
}),
);
// A paid route.
app.get("/api/analytics", yourLogin, async (req, res) => {
if (!(await mesub.hasAccess({ external_id: req.user.id }, "pro"))) {
res.status(402).json({ error: "The Pro plan is needed." });
return;
}
res.json(buildAnalytics(req.user));
});
customer must come from a session your server verified, never from what the browser sends. Next.js and NestJS have the same two pieces, as a route handler and a guard: see the quickstart.
Step 4: put the button on a page
npm install @mesub/react
import { MesubProvider, SubscribeButton } from "@mesub/react";
import "@mesub/react/styles.css";
<MesubProvider endpoint="/api/mesub">
<SubscribeButton plan="pro">Subscribe to Pro</SubscribeButton>
</MesubProvider>;
No React, or your own design? Create the subscription from your server instead, with subscriptions.create in the @mesub/node README, and show the wallet prompt in your own UI.
Try it
Sign in to your site, click the button, and pick the customer's wallet. The wallet asks for two approvals: the terms, then the first payment. Once the payment lands:
/api/analyticsanswers for that customer;- the subscription shows as
activein the dashboard, with its first payment; - the price has left the customer's wallet and arrived in yours.
That is the finish line. You have a paying subscriber.
What happens after the first payment?
Mesub charges every renewal on schedule and handles the ones that fail. Your code stays as it is.
| When | What Mesub does |
|---|---|
| Near the end of each period | Reads the customer's wallet and tells you, with the subscription.renewal_upcoming webhook, whether the next payment can go through. If it cannot and the customer gave an email, Mesub emails them |
| The payment is due | Sends it. Your wallet receives the price, and you get subscription.renewed |
| The payment fails | The subscription turns unpaid. On the Dev and Business tiers, Mesub retries 3 times by default, each retry a quarter of a period later and one day at most, and access stays on while retries run. On Free, you retry by hand and access is off meanwhile |
| Every retry failed | The subscription turns stopped and hasAccess answers false |
| The customer cancels | Access stays on until the end of the period they paid for |
Your gate stays the same through all of it: hasAccess answers from every attempt Mesub made, not only from the account on chain.
Who does what
| Where | What it does | What it holds |
|---|---|---|
| Your pages | Ask the wallet for its two approvals | Nothing secret |
| Your server | Creates subscriptions, checks access, receives webhooks | The API key |
| Mesub | Sends each payment, retries a missed one, records every attempt | No funds |
Mesub holds no funds: each payment moves from the customer's wallet to your receiving wallet.
What to know before you ship
- The customer pays rent in SOL, a deposit for two accounts on Solana: the subscription, which comes back when it is closed, and, the first time they subscribe with a token, its authority, which comes back if they close it. A wallet without enough SOL is refused before anything is signed.
- Prices are fixed per plan. To change a price, publish a new plan. Existing subscribers keep the terms they signed.
- Your login stays yours. Mesub knows your users by the id your server gives it, the wallet that pays, or an email if they give one.
- What it costs. Free: 1 plan and 30 subscribers, no automatic retries, and no access while a payment is late. Dev: 20 USDC every 30 days, 3 plans and 150 subscribers, with automatic retries. Business: custom. A flat price, not a percentage of what you collect. See pricing.
Next
- Check access in detail, from any route.
- Webhooks: be told when a subscription renews, misses a payment or ends.
- Test with the fake Mesub, before any real payment.
- Curious what you would build without Mesub? Mesub or build it yourself.