Subscriptions

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:

  1. Create plan — you publish billing terms (amount, period, payout wallet). RelAI is the plan owner and authorized puller; your wallet is the funds destination.
  2. Subscribe — the subscriber approves the program as a delegate (once per token) and accepts the plan. Terms are snapshotted on-chain.
  3. 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).

POST /subscriptions · Authorization: Bearer <key>
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:

Two-stage subscribe (public, no auth)
// 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 (includes merchantAmount / feeAmount)
  • subscription.payment_failed — a charge failed; the sub is now past_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:

Verify a webhook (Node)
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.

GET /s/:planId/status (public)
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:

Revenue + charge history (authed)
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 + summary

API reference

Method & pathAuthPurpose
POST /subscriptionsBearerCreate + publish a plan
GET /subscriptionsBearerList your plans
POST /subscriptions/:id/deactivateBearerDeactivate a plan
GET /subscriptions/revenueBearerRevenue summary
GET /subscriptions/:id/chargesBearerCharge history
GET /subscriptions/:id/subscribersBearerSubscribers + summary
GET /s/:id/metaPublicPlan terms (subscribe page)
GET /s/:id/status?wallet=PublicSubscription status for a wallet
POST /s/:id/subscribePublicBuild next unsigned subscribe tx
POST /s/:id/confirmPublicConfirm 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.