Recurring USDC billing
Publish a plan, share one link, and auto-charge subscribers each period. Built on Solana's native subscriptions program — non-custodial, funds flow straight to your wallet.
Overview
A subscription is a fixed amount of USDC pulled from a subscriber on a repeating cadence. RelAI orchestrates the flow (storage, scheduler, dashboard, webhooks) on top of the audited on-chain Solana Subscriptions program (De1egAFMkMWZSN5rYXRj9CAdheBamobVNubTsi9avR44). RelAI never custodies funds: each charge moves USDC directly from the subscriber to your wallet.
- One signature to subscribe; renewals are automatic.
- Non-custodial — funds can only land in your whitelisted wallet.
- 5% platform fee, taken atomically in the same transaction.
- Signed webhooks + a status endpoint to gate any service.
How it works
Three on-chain steps, all signed by the right party:
- Create plan — you publish billing terms (amount, period, payout wallet). RelAI is the plan owner and authorized puller; your wallet is the funds destination.
- Subscribe — the subscriber approves the program as a delegate (once per token) and accepts the plan. Terms are snapshotted on-chain.
- Charge — RelAI's scheduler pulls the period's amount each cycle. The program enforces the per-period cap and cadence.
Two models
A — Marketplace API access. Link a plan to one of your RelAI APIs. Active subscribers call the API without the per-call 402 — they send their wallet via the x-subscriber-wallet header and the relay grants access.
B — External service. No RelAI endpoint needed. RelAI is the recurring payment rail; you gate your own product (web app, VM, SaaS) using the status endpoint or webhooks.
Create a plan
From the dashboard (/dashboard/subscriptions), or via the API. Amounts are in token base units (USDC = 6 decimals, so 5000000 = $5). periodHours is the cadence (minimum 1 hour; e.g. 720 ≈ monthly).
curl -X POST https://api.relai.fi/subscriptions \
-H "Authorization: Bearer $RELAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro plan",
"amountBaseUnits": 5000000,
"periodHours": 720,
"merchantWallet": "<your-solana-wallet>",
"network": "solana",
"linkedApiId": "<apiId-or-omit>",
"webhookUrl": "https://your-backend/relai-webhook"
}'The response includes the planId, the on-chain onchainPlanAddress, and (if a webhook is set) a webhookSecret — store it to verify webhook signatures. Share the subscribe link: https://relai.fi/subscribe?plan=<planId>.
Subscribe flow
Subscribing is two transactions, because the on-chain program requires the subscriber's delegate authority to exist before subscribing (its slot-derived id is snapshotted). The hosted /subscribe page handles this automatically. If you build your own UI, call prepareSubscribe repeatedly until it returns the subscribe stage:
// 1) ask the server for the next unsigned tx
POST /s/<planId>/subscribe { "subscriberWallet": "<wallet>" }
// → { stage: "init-authority", wireTransaction } (first time only)
// 2) sign + broadcast it, then call again
POST /s/<planId>/subscribe { "subscriberWallet": "<wallet>" }
// → { stage: "subscribe", wireTransaction, subscriptionPda }
// 3) sign + broadcast, then confirm
POST /s/<planId>/confirm { "subscriberWallet": "<wallet>", "signature": "<sig>" }The first charge (period 0) becomes due immediately and is collected on the next scheduler tick.
Recurring charges
RelAI runs a billing scheduler (the "puller") that charges every due subscription each cycle. The on-chain program is the source of truth: it allows one period's worth of pulls per period and auto-advances when the period elapses, so a subscriber can never be double-charged within a period. A failed charge (e.g. insufficient balance) marks the subscription past_due and is retried later.
Platform fee
RelAI takes a 5% platform fee, deducted in the same transaction as your payout — natively, via two transfers (95% to you, 5% to RelAI). On a $5 charge you receive $4.75. The merchant dashboard shows your net after fee.
Webhooks
Set webhookUrl on a plan to receive signed events. Use them to provision or suspend infrastructure event-driven.
-
subscription.created— a new subscriber accepted the plan -
subscription.charged— a period was successfully billed (includesmerchantAmount/feeAmount) -
subscription.payment_failed— a charge failed; the sub is nowpast_due -
subscription.canceled— the subscriber canceled
Each delivery is signed with the plan's webhookSecret (HMAC-SHA256 over the raw body) in the X-RelAI-Signature header. Verify it:
import crypto from 'crypto'
app.post('/relai-webhook', (req, res) => {
const raw = req.rawBody // the exact bytes received
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.RELAI_WEBHOOK_SECRET)
.update(raw).digest('hex')
if (req.headers['x-relai-signature'] !== expected) return res.status(401).end()
const { type, data } = JSON.parse(raw)
if (type === 'subscription.charged') provision(data.subscriberWallet)
if (type === 'subscription.payment_failed') suspend(data.subscriberWallet)
if (type === 'subscription.canceled') suspend(data.subscriberWallet)
res.sendStatus(200)
})Gate an external service
To gate a web app or VM with no RelAI endpoint, check a wallet's status from your backend. This answers "is this wallet subscribed" — authenticate the wallet in your own app first (e.g. a sign-in signature) before trusting it.
curl "https://api.relai.fi/s/<planId>/status?wallet=<wallet>"
// → { "active": true, "status": "active", "currentPeriodIndex": 3, "nextChargeTs": 1780... }Reporting
The dashboard shows MRR, active subscribers, collected (net), fees paid, past-due, plus per-plan charge history. Programmatically:
GET /subscriptions/revenue // MRR, active subs, collected, fees, past_due, per-plan
GET /subscriptions/<planId>/charges // full charge history for a plan
GET /subscriptions/<planId>/subscribers // subscribers + summaryAPI reference
| Method & path | Auth | Purpose |
|---|---|---|
| POST /subscriptions | Bearer | Create + publish a plan |
| GET /subscriptions | Bearer | List your plans |
| POST /subscriptions/:id/deactivate | Bearer | Deactivate a plan |
| GET /subscriptions/revenue | Bearer | Revenue summary |
| GET /subscriptions/:id/charges | Bearer | Charge history |
| GET /subscriptions/:id/subscribers | Bearer | Subscribers + summary |
| GET /s/:id/meta | Public | Plan terms (subscribe page) |
| GET /s/:id/status?wallet= | Public | Subscription status for a wallet |
| POST /s/:id/subscribe | Public | Build next unsigned subscribe tx |
| POST /s/:id/confirm | Public | Confirm after broadcast |
FAQ
Does the subscriber need SOL? A small amount once, to set up the delegate authority + sign the subscribe. RelAI fee-pays the recurring pulls.
What tokens? USDC by default (any SPL token is supported by the program; Token-2022 confidential transfers are on the roadmap).
What's the shortest period? 1 hour (cadence is in whole hours).
Can a subscriber cancel? Yes — cancellation is subscriber-signed; no further charges occur.