Skip to content

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.

ts
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.

ts
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).

ts
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 TariaPayEmbedError is thrown (code: "popup_blocked") — always call from a click handler, and be prepared to fall back to mode: "redirect".
  • Use onComplete for instant UI updates only — never grant entitlements based on it. Whether the subscription is actually active is determined by subscription.* 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:

ts
import { mountSubscribeButtons } from "@tariapay/sdk/browser";

mountSubscribeButtons({ checkoutBaseUrl: "https://checkout.tariapay.com" });
html
<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

bash
# 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:

EventMeaning
subscription.createdFirst payment confirmed, subscription activated
subscription.renewedRenewal payment for a subsequent period confirmed
subscription.past_duePeriod ended without renewal, or a confirmed payment was affected by a chain reorg
subscription.canceledSubscription canceled (immediately, or at period end for scheduled cancellation)
subscription.expiredGrace 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.

ts
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

MethodPathPurpose
GET/subscriptions?customerId=user_42Look up subscriptions by your own customer ID
GET/subscriptions/:idRetrieve a single subscription
GET/subscriptions/:id/invoicesList a subscription's billing-period invoices
POST/subscriptions/:id/cancelCancel 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:

ts
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