Subscriptions & Rebilling
Time : ~10 minutes | Difficulty : Intermediate
What Are Subscriptions in TagadaPay?
TagadaPay manages the entire recurring billing lifecycle — creation, automatic rebills, retries on failure, pause/resume, cancellation, and processor changes. It’s PSP-agnostic : the same subscription can be billed through Stripe today and NMI tomorrow, without touching your code.
┌────────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────┐
│ Customer │────▶│ Subscribe │────▶│ Auto-rebill │────▶│ Renew │
│ pays once │ │ (active) │ │ every cycle │ │ forever │
└────────────┘ └─────────────┘ └──────────────┘ └──────────┘
│ │
│ ┌─────┴──────┐
│ │ Declined? │
│ │ → Retry │
│ │ → Past due │
│ │ → Cancel │
│ └────────────┘
│
┌────┴────┐
│ Trial? │
│ 7 days │
│ free │
└─────────┘
Subscriptions are vaulted. The customer’s card is tokenized once. TagadaPay charges it automatically each cycle — through whichever processor your payment flow selects. You never handle raw card data or PSP-specific recurring APIs.
Subscription Lifecycle
Status Meaning trialingFree trial period, no charges yet activeRecurring charges are running past_dueLast rebill failed, retrying automatically pausedBilling suspended, can be resumed canceledTerminated (immediately or at period end)
create → trialing → active ⟲ (rebill each cycle)
↓
past_due → retry → active (success)
↓ → canceled (max retries)
↓
paused → resume → active
↓
canceled
Create a Subscription
import Tagada from '@tagadapay/node-sdk' ;
const tagada = new Tagada ( 'your-api-key' );
const { subscription } = await tagada . subscriptions . create ({
customerId: 'cus_abc123' ,
priceId: 'price_monthly_2999' ,
storeId: 'store_xxx' ,
currency: 'USD' ,
quantity: 1 ,
defaultPaymentInstrumentId: 'pi_abc123' ,
});
console . log ( subscription . id ); // 'sub_xyz789'
console . log ( subscription . status ); // 'active'
console . log ( subscription . nextBillingDate ); // '2026-04-19T00:00:00Z'
Parameter Required Description customerIdYes The customer to bill priceIdYes A recurring price (from a product variant) storeIdYes Your store currencyYes ISO currency code defaultPaymentInstrumentIdNo Card to charge (uses customer’s default if omitted) quantityNo Number of units (default: 1) paymentIdNo Link to the initial payment billingCycleAnchorNo ISO date — fix the billing day (e.g. always the 1st) initiatePaymentNo Charge immediately on creation
Create with a Free Trial
const { subscription } = await tagada . subscriptions . create ({
customerId: 'cus_abc123' ,
priceId: 'price_monthly_2999' ,
storeId: 'store_xxx' ,
currency: 'USD' ,
defaultPaymentInstrumentId: 'pi_abc123' ,
trialSettings: {
trialPeriodDuration: 7 ,
trialPeriodDurationType: 'day' ,
},
});
console . log ( subscription . status ); // 'trialing'
console . log ( subscription . trial ); // true
The customer isn’t charged until the trial ends. TagadaPay automatically transitions to active and triggers the first rebill.
Trial Duration Type Examples day3 days, 7 days, 14 days week1 week, 2 weeks month1 month, 3 months
Manual Rebill
Trigger an immediate charge on any active or past_due subscription — useful for dashboards, support tools, or retry scripts.
const result = await tagada . subscriptions . rebill ( 'sub_xyz789' );
console . log ( result . success ); // true
console . log ( result . message ); // 'Rebill triggered successfully'
What happens under the hood:
TagadaPay loads the subscription’s price, currency, and payment instrument
Routes the charge through your payment flow (cascade, weighted, fallback — all automatic)
On success → updates nextBillingDate, status stays active
On decline → enters retry logic (see below)
When to Use Manual Rebill
Scenario Example Customer updated card They fixed their card details, you want to charge immediately instead of waiting for auto-retry Support request ”Please charge me now” Retry script Batch-retry all past_due subscriptions Testing Verify billing works end-to-end
Batch Retry All Past-Due Subscriptions
const { items : subscriptions } = await tagada . subscriptions . list ({
per_page: 100 ,
filters: {
'subscriptions.status' : [ 'past_due' ],
storeIds: [ 'store_xxx' ],
},
});
console . log ( `Found ${ subscriptions . length } past-due subscriptions` );
for ( const sub of subscriptions ) {
try {
await tagada . subscriptions . rebill ( sub . id );
console . log ( `Rebilled ${ sub . id } ` );
} catch ( err ) {
console . log ( `Failed ${ sub . id } : ${ err . message } ` );
}
}
Automatic Retry on Failure
When a rebill is declined, TagadaPay doesn’t immediately cancel. It retries automatically:
Rebill declined
→ Wait 24h → Retry #1
→ Wait 48h → Retry #2
→ Wait 96h → Retry #3
→ Wait 168h → Retry #4
→ Wait 168h → Retry #5
→ Cancel subscription
Retry Delay Total elapsed 1 24 hours 1 day 2 48 hours 3 days 3 96 hours 7 days 4 168 hours 14 days 5 168 hours 21 days
During retries, the subscription status is past_due. After max retries, it’s automatically canceled.
Smart retry is also available — it uses failure codes (e.g. “insufficient funds”), velocity limits, and even payroll-day heuristics to pick the optimal retry time. Contact support to enable it.
Cancel a Subscription
await tagada . subscriptions . cancel ({
subscriptionIds: [ 'sub_xyz789' ],
});
// Status → 'canceled' right now
Cancel at Period End
await tagada . subscriptions . cancel ({
subscriptionIds: [ 'sub_xyz789' ],
cancelAtPeriodEnd: true ,
});
// Stays 'active' until currentPeriodEnd, then auto-cancels
The customer keeps access until their billing period ends. TagadaPay handles the scheduled cancellation automatically.
Bulk Cancel
await tagada . subscriptions . cancel ({
subscriptionIds: [ 'sub_001' , 'sub_002' , 'sub_003' ],
cancelAtPeriodEnd: true ,
});
Resume a Subscription
Resume a canceled, paused, or past_due subscription:
await tagada . subscriptions . resume ({
subscriptionIds: [ 'sub_xyz789' ],
});
// Status → 'active', billing resumes
If the subscription had cancelAtPeriodEnd: true but hasn’t reached the end yet, resuming clears the cancellation flag.
Pause a Subscription
Pausing stops billing without canceling. The subscription can be resumed later.
Pause is available via the dashboard and internal API. The SDK exposes cancel + resume for the same effect — cancel at period end, then resume when ready.
Change Processor
Move a subscription from one processor to another — for example, migrating from Stripe to Checkout.com, or clearing a stuck processor.
Assign a Specific Processor
const { subscription } = await tagada . subscriptions . changeProcessor (
'sub_xyz789' ,
'proc_cko' , // Checkout.com
);
console . log ( subscription . processorId ); // 'proc_cko'
The next rebill will use Checkout.com instead of the previous processor.
Let TagadaPay Auto-Select
const { subscription } = await tagada . subscriptions . changeProcessor ( 'sub_xyz789' );
// No processorId → TagadaPay uses the payment flow strategy
This removes the sticky processor, so the next rebill will be routed by your payment flow (weighted, cascade, etc.).
Use Case: Migrate All Subscriptions from Stripe to NMI
const { items : subscriptions } = await tagada . subscriptions . list ({
per_page: 100 ,
filters: {
'subscriptions.status' : [ 'active' ],
processorIds: [ 'proc_s1' ], // current: Stripe
},
});
for ( const sub of subscriptions ) {
await tagada . subscriptions . changeProcessor ( sub . id , 'proc_nmi' );
console . log ( `Migrated ${ sub . id } to NMI` );
}
This is a TagadaPay superpower. With a traditional PSP, migrating subscriptions between processors means re-collecting card details. With TagadaPay’s PSP-agnostic vault, you just change the processor ID — the vaulted card works across all processors.
List & Filter Subscriptions
const { items , pageInfo } = await tagada . subscriptions . list ({
page: 1 ,
per_page: 50 ,
sort: 'createdAt' ,
sort_order: 'desc' ,
filters: {
storeIds: [ 'store_xxx' ],
'subscriptions.status' : [ 'active' , 'trialing' ],
},
});
for ( const sub of items ) {
console . log ( ` ${ sub . id } — ${ sub . status } — next bill: ${ sub . nextBillingDate } ` );
}
Available Filters
Filter Type Example subscriptions.statusstring[]['active', 'past_due']subscriptions.trialbooleantruestoreIdsstring[]['store_xxx']processorIdsstring[]['proc_s1', 'proc_nmi']currencystring'USD'customerEmailstring'jane@example.com'price{ condition, value }{ condition: 'gte', value: [1000] }date{ condition, value }{ condition: 'between', value: ['2026-01-01', '2026-03-01'] }nextBillingDate{ condition, value }{ condition: 'lte', value: ['2026-03-25'] }
Retrieve a Subscription
const { subscription } = await tagada . subscriptions . retrieve ( 'sub_xyz789' );
console . log ( subscription . status ); // 'active'
console . log ( subscription . currentPeriodStart ); // '2026-03-01T00:00:00Z'
console . log ( subscription . currentPeriodEnd ); // '2026-04-01T00:00:00Z'
console . log ( subscription . nextBillingDate ); // '2026-04-01T00:00:00Z'
console . log ( subscription . processorId ); // 'proc_s1'
console . log ( subscription . paymentInstrumentId ); // 'pi_abc123'
console . log ( subscription . cancelAtPeriodEnd ); // false
Webhook Events
Listen for subscription events via webhooks to keep your system in sync:
Event When it fires subscription/createdNew subscription created subscription/rebillSucceededRecurring charge succeeded subscription/rebillDeclinedRecurring charge failed subscription/pastDueSubscription entered past_due after decline subscription/pausedSubscription paused subscription/resumedSubscription resumed subscription/canceledSubscription canceled immediately subscription/cancelScheduledCancel scheduled for period end subscription/rebillUpcomingUpcoming rebill (advance notice)
Full Example: Subscription with Trial, Rebill, and Processor Migration
import Tagada from '@tagadapay/node-sdk' ;
const tagada = new Tagada ( 'your-api-key' );
const storeId = 'store_xxx' ;
// ── 1. Create a subscription with 14-day trial ──────────────────────
const { subscription } = await tagada . subscriptions . create ({
customerId: 'cus_abc123' ,
priceId: 'price_monthly_2999' ,
storeId ,
currency: 'USD' ,
defaultPaymentInstrumentId: 'pi_abc123' ,
trialSettings: {
trialPeriodDuration: 14 ,
trialPeriodDurationType: 'day' ,
},
});
console . log ( `Created: ${ subscription . id } — ${ subscription . status } ` );
// → "Created: sub_xyz789 — trialing"
// ── 2. Later: trial ends, auto-rebill runs ──────────────────────────
// (TagadaPay handles this automatically — no code needed)
// ── 3. Manual rebill (e.g. customer updated their card) ─────────────
const rebillResult = await tagada . subscriptions . rebill ( subscription . id );
console . log ( `Rebill: ${ rebillResult . message } ` );
// ── 4. Move subscription from Stripe to Checkout.com ────────────────
const { subscription : updated } = await tagada . subscriptions . changeProcessor (
subscription . id ,
'proc_cko' ,
);
console . log ( `Processor changed to: ${ updated . processorId } ` );
// ── 5. Customer wants to cancel at period end ───────────────────────
await tagada . subscriptions . cancel ({
subscriptionIds: [ subscription . id ],
cancelAtPeriodEnd: true ,
});
console . log ( 'Cancel scheduled at period end' );
// ── 6. Customer changes their mind — resume ─────────────────────────
await tagada . subscriptions . resume ({
subscriptionIds: [ subscription . id ],
});
console . log ( 'Subscription resumed!' );
Next Steps
Multi-PSP Routing & Vault Connect Stripe, NMI, Checkout.com, Airwallex and route payments intelligently
Headless Payments (Frontend) Build your own checkout UI with client-side card tokenization and 3DS
Node SDK Quick Start Get started with stores, products, and funnels
API Reference Full REST API documentation