AbsolutePayDocs

Subscriptions

Recurring billing — create a plan, subscribe a customer, and track each cycle's deduction.

Bill a customer on a schedule. The model is plan → subscription → recurring deductions: define a plan once, subscribe any number of customers to it, and each billing cycle produces a deduction that fires the same payment.succeeded webhook as a one-off charge.

Creating plans and subscriptions needs the subscriptions:write scope; reads need subscriptions:read.

1. Create a plan

A plan is the reusable template: the per-cycle amount, the billing interval, and how many cycles to bill in total.

const plan = await ap.subscriptions.createPlan({
  merchantPlanNo: "pro-monthly",  // your unique plan reference
  name: "Pro (monthly)",
  amount: { amount: "9.99", currency: "USDT" }, // decimal string, ≤ 6 dp
  interval: "MONTH",   // DAY | WEEK | MONTH | YEAR
  intervalCount: 1,    // e.g. MONTH × 3 = quarterly
  totalCycles: 12,     // total charges before the plan completes
  // trialDays: 14,    // optional free trial: no charge for N days after the customer authorizes
});
console.log(plan.planNo);
plan = ap.subscriptions.create_plan(
    merchant_plan_no="pro-monthly",  # your unique plan reference
    name="Pro (monthly)",
    amount={"amount": "9.99", "currency": "USDT"},  # decimal string, ≤ 6 dp
    interval="MONTH",   # DAY | WEEK | MONTH | YEAR
    interval_count=1,   # e.g. MONTH × 3 = quarterly
    total_cycles=12,    # total charges before the plan completes
    # trial_days=14,    # optional free trial: no charge for N days after the customer authorizes
)
print(plan["planNo"])
plan, err := ap.Subscriptions.CreatePlan(ctx, absolutepay.PlanParams{
	MerchantPlanNo: "pro-monthly", // your unique plan reference
	Name:           "Pro (monthly)",
	Amount:         absolutepay.Money{Amount: "9.99", Currency: "USDT"}, // decimal string, ≤ 6 dp
	Interval:       "MONTH", // DAY | WEEK | MONTH | YEAR
	IntervalCount:  1,       // e.g. MONTH × 3 = quarterly
	TotalCycles:    12,      // total charges before the plan completes
	// TrialDays:   14,      // optional free trial: no charge for N days after the customer authorizes
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(plan["planNo"])
BODY='{"merchantPlanNo":"pro-monthly","name":"Pro (monthly)","amount":{"amount":"9.99","currency":"USDT"},"interval":"MONTH","intervalCount":1,"totalCycles":12}'
curl https://api.absolutepay.io/v1/subscription-plans \
  -H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
  $(sign POST /v1/subscription-plans "$BODY") -d "$BODY"
{ "planNo": "plan_01JX…", "merchantPlanNo": "pro-monthly", "name": "Pro (monthly)", "amount": { "amount": "9.99", "currency": "USDT" }, "interval": "MONTH", "intervalCount": 1, "totalCycles": 12 }

Keep the returned planNo — it's what you pass when subscribing a customer. merchantPlanNo must be unique per workspace (a duplicate is a 409). List existing plans with ap.subscriptions.listPlans() / GET /v1/subscription-plans.

Free trial. Set trialDays on the plan (0–365) to give every subscriber a trial: the customer authorizes but isn't charged until the trial ends, then billing begins automatically. A subscription in its trial reports the TRIALING status. Omit trialDays (or 0) to bill immediately on authorization.

2. Subscribe a customer

const sub = await ap.subscriptions.create({
  merchantSubNo: "sub-1001",     // your reference + idempotency key
  planNo: plan.planNo,
  // callbackUrl: "https://…",   // optional per-subscription notification URL
});
console.log(sub.subscribeUrl); // send the customer here to authorize
sub = ap.subscriptions.create(
    merchant_sub_no="sub-1001",   # your reference + idempotency key
    plan_no=plan["planNo"],
    # callback_url="https://…",   # optional per-subscription notification URL
)
print(sub["subscribeUrl"])  # send the customer here to authorize
sub, err := ap.Subscriptions.Create(ctx, absolutepay.SubscribeParams{
	MerchantSubNo: "sub-1001", // your reference + idempotency key
	PlanNo:        plan["planNo"].(string),
	// CallbackURL: "https://…", // optional per-subscription notification URL
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(sub["subscribeUrl"]) // send the customer here to authorize
BODY='{"merchantSubNo":"sub-1001","planNo":"plan_01JX…"}'
curl https://api.absolutepay.io/v1/subscriptions \
  -H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
  $(sign POST /v1/subscriptions "$BODY") -d "$BODY"
{ "merchantSubNo": "sub-1001", "subId": "…", "status": "PENDING", "planNo": "plan_01JX…", "subscribeUrl": "https://…" }

The subscription starts PENDING. Send the customer to the hosted subscribeUrl to authorize; once they do, it goes ACTIVE and bills automatically each cycle (or TRIALING first, if the plan has a trial). merchantSubNo is your idempotency key — re-POSTing the same one won't create a duplicate subscription.

Telling integrations apart

A subscription created with an API key carries an appId — the id of the key that created it — so a workspace running several integrations can attribute each subscription to the right one. Subscriptions created from the dashboard have no appId.

3. Track the cycles

Each successful cycle fires a payment.succeeded webhook — handle it exactly like a one-off payment (idempotent on event.id). To reconcile or backfill, pull the per-cycle history:

const history = await ap.subscriptions.deductions("sub-1001");
// each deduction: paymentOrderNo, amount, status (SUCCESS | FAILED | PENDING | BLOCKED), ts
history = ap.subscriptions.deductions("sub-1001")
# each deduction: paymentOrderNo, amount, status (SUCCESS | FAILED | PENDING | BLOCKED), ts
history, err := ap.Subscriptions.Deductions(ctx, "sub-1001")
if err != nil {
	log.Fatal(err)
}
fmt.Println(history) // paymentOrderNo, amount, status (SUCCESS | FAILED | PENDING | BLOCKED), ts
curl "https://api.absolutepay.io/v1/subscriptions/sub-1001/deductions" \
  -H "Authorization: Bearer $APP_TOKEN" $(sign GET /v1/subscriptions/sub-1001/deductions "")

List all subscriptions with ap.subscriptions.list() / GET /v1/subscriptions — keyset-paginated (limit, before, optional status filter; see Pagination).

4. Cancel

await ap.subscriptions.cancel("sub-1001"); // stop all future billing
ap.subscriptions.cancel("sub-1001")  # stop all future billing
_, err := ap.Subscriptions.Cancel(ctx, "sub-1001") // stop all future billing
if err != nil {
	log.Fatal(err)
}
curl -X POST https://api.absolutepay.io/v1/subscriptions/sub-1001/cancel \
  -H "Authorization: Bearer $APP_TOKEN" $(sign POST /v1/subscriptions/sub-1001/cancel "")

Cancelling stops future cycles only — settled deductions are crypto payments and can't be clawed back. To return one, issue a refund.

Subscription statuses

PENDING — created, waiting for the customer to authorize on subscribeUrl. TRIALING — authorized and in its free trial; billing hasn't started yet. ACTIVE — billing each cycle. PAST_DUE — a cycle's charge failed. COMPLETED — all totalCycles billed. CANCELLED / BLOCKED — no further billing.

Next

  • Webhooks — receive each cycle's payment.succeeded reliably.
  • Fees — how platform fees are disclosed. Subscription cycle fees are settled per cycle and aren't priced by the preview endpoint.
  • Refunds — return a settled cycle's charge.
  • API reference — full parameter and response tables for every subscription endpoint.

On this page