Subscriptions (API)
The Subscriptions API connects the recurring charges in your own system to stablecoin payments on Taria Pay. Your server creates plans and opens subscriptions for your own customers, then syncs subscription status back to your system through webhooks.
Who this is for
Merchants who already have their own product, pricing, and customer system (SaaS, membership platforms, B2B services). You manage users and entitlements in your own system; Taria Pay collects payment for each billing period and reports the status back.
If you don't have your own system and just want a shareable subscription page, use subscription plans in the Dashboard (no-code) — no coding required.
Important boundary
The current flow is customer-initiated renewal — Taria Pay does not automatically charge the customer's wallet. What the Subscriptions API gives you is:
- Open a subscription for a customer and get a payment link to hand them for the current billing period;
- Sync subscription status through webhooks, so you can grant or revoke entitlements in your own system.
This is not automatic recurring billing with wallet debits. True automatic charging requires an additional authorization model, which is not yet available.
Who makes the requests
Requests must come from your server. API keys must only be stored server-side.
Two-step integration
1. Create a plan (price object)
A plan is a reusable price definition (amount + currency + billing interval). Create it once and open subscriptions for as many customers as you need.
const plan = await tariapay.subscriptionPlans.create({
name: "Pro",
amount: "10",
currency: "USDC",
interval: "monthly", // weekly | monthly | quarterly | yearly
});2. Open a subscription for your customer
Pass the planId and your own customer identifier (merchantCustomerId, optionally with customerEmail). The response contains the subscription, the first billing-period invoice, and a payment order with a checkoutUrl — hand the checkoutUrl to the customer to pay for the current period.
const { subscription, invoice, paymentIntent } = await tariapay.subscriptions.create({
planId: plan.id,
merchantCustomerId: "user_42", // your system's user ID, echoed back in webhooks
customerEmail: "buyer@example.com", // optional: used for renewal reminder emails
});
return { checkoutUrl: paymentIntent.checkoutUrl };Calling again with the same (plan, customer) pair reuses the same subscription instead of creating a duplicate; while the current invoice is unpaid, the same payment order is reused too, so the customer is never billed twice for one period.
Embedding a subscribe entry point on your page (browser SDK)
The paymentIntent.checkoutUrl from step 2 is a hosted checkout page. You can redirect to it directly, or use @tariapay/sdk/browser to open it as a popup on your own page — the buyer never leaves your site, and the hosted page handles wallet connection, signing, and payment (you never touch wallet logic).
Popup mode (recommended)
import { subscribe } from "@tariapay/sdk/browser";
button.addEventListener("click", async () => {
// Call your own server to open the subscription and get back the checkoutUrl (see step 2 above)
const { checkoutUrl } = await fetch("/api/subscribe", { method: "POST" }).then((r) =>
r.json(),
);
await subscribe({
checkoutUrl,
mode: "popup", // popup by default; use "redirect" for a full-page redirect
onComplete: () => showActivatedUI(), // instant UI feedback only — do not grant entitlements here
onClose: () => {}, // buyer closed the popup without completing
});
});mode: "popup"(default) opens the hosted checkout in a popup so the buyer stays on your page;mode: "redirect"performs a full-page redirect.- If the browser blocks the popup, a
TariaPayEmbedErroris thrown (code: "popup_blocked") — always call from a click handler, and be prepared to fall back tomode: "redirect". - Use
onCompletefor instant UI updates only — never grant entitlements based on it. Whether the subscription is actually active is determined bysubscription.*webhooks (see below). The SDK only accepts completion messages that come from the checkout page's origin and from the exact window it opened, so other pages cannot forge them.
Declarative buttons (no server code)
If you're using plans created in the Dashboard (which have a slug), you can use mountSubscribeButtons() to automatically attach click handlers to buttons on your page — no server-side subscription logic needed:
import { mountSubscribeButtons } from "@tariapay/sdk/browser";
mountSubscribeButtons({ checkoutBaseUrl: "https://checkout.tariapay.com" });<button
data-tariapay-plan="pro-monthly"
data-tariapay-mode="popup"
data-tariapay-customer-id="user_42"
data-tariapay-email="buyer@example.com"
>
Subscribe with stablecoins
</button>Each button can be overridden with data-tariapay-base, data-tariapay-mode, data-tariapay-customer-id, and data-tariapay-email; data-tariapay-customer-id is echoed back in webhooks as merchantCustomerId, and data-tariapay-email prefills the buyer's email.
REST examples
# Create a plan
curl -X POST "https://api.tariapay.com/v1/subscription-plans" \
-H "Authorization: Bearer tpk_..." \
-H "Content-Type: application/json" \
-d '{ "name": "Pro", "amount": "10", "currency": "USDC", "interval": "monthly" }'
# Open a subscription for a customer
curl -X POST "https://api.tariapay.com/v1/subscriptions" \
-H "Authorization: Bearer tpk_..." \
-H "Content-Type: application/json" \
-d '{ "planId": "plan_...", "merchantCustomerId": "user_42" }'Renewals
Nothing is charged automatically when a billing period ends. There are two renewal mechanisms, and you can use both:
- Listen to subscription webhooks and remind customers from your own system to come back and pay before the period ends;
- If the customer provided an email, Taria Pay sends a one-click renewal email before the period ends.
When the customer pays again, the payment is recorded as a renewal invoice under the same subscription and the subscription period is extended.
Syncing status with webhooks
Treat webhooks as the source of truth for your business records — don't rely solely on whether the customer lands back on your success page. Subscription events:
| Event | Meaning |
|---|---|
subscription.created | First payment confirmed, subscription activated |
subscription.renewed | Renewal payment for a subsequent period confirmed |
subscription.past_due | Period ended without renewal, or a confirmed payment was affected by a chain reorg |
subscription.canceled | Subscription canceled (immediately, or at period end for scheduled cancellation) |
subscription.expired | Grace period passed without renewal; subscription expired automatically |
The payload's data contains subscription, invoice, and paymentIntent (for lifecycle events subscription.canceled / past_due / expired, invoice and paymentIntent may be null). subscription.merchantCustomerId echoes back your customer ID unchanged.
const event = tariapay.webhooks.constructEvent(
rawBody,
req.headers,
process.env.TARIAPAY_WEBHOOK_SECRET!,
);
switch (event.type) {
case "subscription.created":
case "subscription.renewed":
// event.data.subscription.merchantCustomerId: grant/extend entitlements
break;
case "subscription.past_due":
// pause entitlements or remind the customer to pay again
break;
case "subscription.canceled":
case "subscription.expired":
// revoke entitlements
break;
default:
break;
}Querying and canceling
| Method | Path | Purpose |
|---|---|---|
GET | /subscriptions?customerId=user_42 | Look up subscriptions by your own customer ID |
GET | /subscriptions/:id | Retrieve a single subscription |
GET | /subscriptions/:id/invoices | List a subscription's billing-period invoices |
POST | /subscriptions/:id/cancel | Cancel a subscription (immediate by default; pass { "atPeriodEnd": true } to cancel at period end) |
By default, cancellation takes effect immediately and entitlements should be revoked. To let the customer keep access until the end of the current paid period, pass atPeriodEnd: true — the subscription stays active with a cancelAtPeriodEnd flag, and the system transitions it to canceled at the end of the period:
await tariapay.subscriptions.cancel("sub_...", { atPeriodEnd: true });Subscription statuses
incomplete: subscription created, first payment not yet confirmed.active: the most recent period's payment is confirmed and the current period is valid.past_due: the period ended without renewal (or a confirmed payment was reorged); payment needs to be collected again, and the subscription can still recover within the grace period.canceled: canceled (immediately, or at period end for scheduled cancellation).expired: grace period passed without renewal; automatically expired.
Next steps
- Verify signatures and handle events: Receiving payment results
- Full API fields: API reference