Overview
Subscription APIs enable you to manage recurring payment agreements with customers. Subscriptions automate billing cycles for products or services, creating predictable recurring revenue for your business.
What is a Subscription?
A subscription represents a recurring payment agreement between you and your customer for a product or service. Subscriptions are created automatically when a customer completes a payment for a product with a selling plan (subscription plan). The Subscription API allows you to manage subscription lifecycles, including pausing, resuming, and canceling subscriptions.
Each subscription:
- 🔄 Automates recurring billing cycles (weekly, monthly, yearly)
- 💰 Links to a product with a selling plan
- 👤 Associates with a customer
- 📅 Tracks billing periods and payment status
- 🏷️ Supports metadata for custom tracking
🔗 Related APIs: Subscriptions are created from Products with selling plans, linked to Customers, and initiated through Payment Intents.
Key Features
| Feature | Description |
|---|---|
| Automatic Billing | Charges customers automatically each billing cycle |
| Lifecycle Management | Pause, resume, cancel subscriptions |
| Flexible Plans | Support weekly, monthly, yearly billing intervals |
| Payment Tracking | Monitor payment status and history |
| Grace Periods | Handle failed payments with retry logic |
| Proration | Handle mid-cycle plan changes with automatic pro-rata calculation |
| Webhook Notifications | Real-time updates on subscription events |
Use Cases
Common Scenarios:
- 💼 SaaS Products: Monthly/annual software subscriptions
- 📰 Content Platforms: News, media, streaming subscriptions
- 🏋️ Memberships: Gym, club, community memberships
- 📦 Subscription Boxes: Monthly product deliveries
- 🎓 Online Courses: Recurring access to educational content
- 🛡️ Protection Plans: Extended warranties, insurance
How Subscriptions Work
Create Product → Add Selling Plan → Customer Pays → Subscription Active → Auto-Billing
↓ ↓ ↓ ↓ ↓
Product Subscription Payment Active Recurring
(1-time) Plan Created Complete Status Charges
Workflow:
- Create Product with selling plan (subscription option)
- Customer Subscribes via Payment Link or Payment Intent
- Initial Payment processed to activate subscription
- Subscription Active begins billing cycle
- Automatic Billing at each cycle (weekly/monthly/yearly)
- Manage Lifecycle pause, resume, or cancel as needed
Important Notes
⚠️ Subscriptions Are Auto-Created: There is NO direct POST /subscriptions endpoint. Subscriptions are created automatically when a customer completes payment for a product with a selling plan.
Creation Methods:
- Via Payment Intent API with
selling_plan_id - Via Payment Link with subscription products
- Automatically upon successful first payment
Introduction to Subscriptions
Subscriptions in MartianPay enable recurring revenue through automated billing cycles. Key concepts include:
Subscription Lifecycle
Subscriptions go through several states during their lifecycle:
-
incomplete: Subscription created but initial payment not yet completed
- Has
payment_required: trueand apayment_urlfor customer payment - Automatically expires after a set period if not paid
- Check
payment_expires_atandhours_since_creationfor urgency
- Has
-
active: Subscription is active and billing normally
- Customer has completed payment
- Recurring charges happen automatically at each billing cycle
current_period_startandcurrent_period_enddefine the billing period
-
paused: Subscription temporarily paused
- No charges generated during pause period
- Can be configured to auto-resume at
resumes_attimestamp pause_collection_behaviordetermines how pending invoices are handled
-
past_due: Payment failed but subscription not yet canceled
- Recent payment attempt failed
- System may retry payment automatically
- Check
latest_invoice_idfor failed invoice details
-
canceled: Subscription has been canceled
- No future charges will be created
canceled_atshows when cancellation occurredcancel_reasonmay contain customer-provided reason- If
cancel_at_period_end: true, subscription remains active until period ends
How Subscriptions Are Created
Important: There is NO direct POST /subscriptions endpoint. Subscriptions are created automatically through one of the following methods:
Method 1: Payment Intent with Selling Plan (Backend API)
Create a payment intent that references a payment link with a selling plan product:
curl --location --request POST 'https://api.martianpay.com/v1/payment_intents' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data '{
"payment_link_id": "plink_abc123",
"primary_variant": {
"quantity": 1,
"variant_id": "var_xyz789",
"selling_plan_id": "sp_plan123"
},
"addons": [],
"description": "Monthly subscription plan",
"receipt_email": "customer@example.com",
"return_url": "https://yoursite.com/thank-you",
"complete_on_first_payment": true,
"product_version": 12,
"shipping_address": {
"line1": "123 Main St",
"line2": "Apt 4B",
"city": "San Francisco",
"state": "CA",
"postal_code": "94102",
"country": "US"
},
"customer": "cus_existingCustomer123",
"metadata": {
"order_id": "order_123",
"source": "website"
},
"merchant_order_id": "order_123"
}'
Key Fields for Subscription Creation:
payment_link_id: ID of the payment link containing the subscription productselling_plan_id: The specific subscription plan the customer selectedvariant_id: The product variant being subscribed tocomplete_on_first_payment: Set totrueto create subscription immediately after first paymentproduct_version: Version of the product configurationcustomer: Existing customer ID (or omit to create new customer)metadata: Custom data for tracking (keys and values are flexible)merchant_order_id: Your unique order identifier
Payment Processing After Creating Payment Intent:
After creating the payment intent with the request above, the payment processing flow is identical to the standard payment flow documented in Payment Intent APIs:
- Update Payment Method - Customer selects crypto or fiat payment method
- Payment Execution - Customer completes payment (blockchain transfer or card payment)
- Payment Confirmation - System confirms payment and updates payment intent status
- Subscription Creation - Once payment is confirmed, subscription is automatically created
For detailed payment processing steps, including:
- Updating payment method (crypto/fiat selection)
- Handling payment confirmation
- Tracking payment status
- Error handling
Please refer to the How to Use Payment APIs documentation, specifically:
- Section 2: Update Payment Intent (for payment method selection)
- Section 4: Get Payment Intent (for checking payment status)
- Section 6: Cancel Payment Intent (if needed)
The only difference is that for subscription products, a subscription will be automatically created after successful payment completion.
Method 2: Direct Payment Link Payment (Frontend/Hosted)
Customers can directly visit and pay on a payment link URL that contains subscription products:
- Create a payment link with a subscription product (see Payment Link APIs)
- Share the payment link URL with your customer (e.g.,
https://buy.martianpay.com/{payment_link_id}) - Customer Authentication: Customer enters their email address and receives an OTP (One-Time Password) code
- Customer enters the OTP code to log in
- Customer selects a selling plan and completes payment
- Subscription is automatically created upon successful payment
Note:
- When using payment links, the customer can choose from available selling plans if the product has multiple subscription options
- This method requires customer email authentication via OTP for security
- If you want to skip the email/OTP login step, use Method 3 (Ephemeral Token) below
Method 3: Payment Link with Ephemeral Token (Third-Party Auth Integration)
For seamless integration with external authentication systems (Instagram, WhatsApp, your own platform), you can use ephemeral tokens to authenticate customers automatically without requiring them to log in again.
Step 1: Generate Ephemeral Token
curl --location --request POST 'https://api.martianpay.com/v1/customers/ephemeral_tokens' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data '{
"allow_create": true,
"channel_metadata": {
"platform_user_id": "user_12345",
"source": "whatsapp_bot"
},
"idp_key": "email",
"idp_subject": "customer@example.com",
"issued_by": "whatsapp_commerce_bot",
"provider": "whatsapp",
"return_url": "https://example.com/subscription/success"
}'
Response:
{
"expires_at": 1735689600,
"token": "eph_abc123xyz789"
}
Step 2: Share Payment Link with Token
Append the ephemeral token to your payment link URL:
https://buy.martianpay.com/{payment_link_id}?ephemeral_token=eph_abc123xyz789
Step 3: Customer Completes Payment
When the customer clicks the link:
- They're automatically authenticated (no login required)
- They see the subscription product and select a selling plan
- They complete payment through the hosted checkout
- Subscription is automatically created upon successful payment
Step 4: Track Subscription Status
After successful payment, you have two ways to track the created subscription:
-
Via Webhook (Recommended):
- Listen for
payment_intent.succeededwebhook event - Extract
merchant_order_idfrom the payment intent payload - Use this
merchant_order_idto map the payment to your order system
- Listen for
-
Via List Subscription API:
- Query subscriptions using the
external_idparameter - Example:
curl --location --request GET 'https://api.martianpay.com/v1/subscriptions?external_id={order_id}' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'- This returns all subscriptions associated with the specified
order_id - Useful for reconciliation and status checking after payment
- Query subscriptions using the
Benefits:
- No additional login step - seamless customer experience
- Works with any external identity provider (email, phone, or UUID-based)
- Supports anonymous tokens for order-only tracking (without customer identity)
- Secure with short-lived tokens (5-15 minutes)
- Auto-creates new customers if they don't exist (
allow_create: true) - Tracks platform-specific data through
channel_metadata - Can associate tokens with specific orders via
order_idparameter
Use Cases:
- Instagram Commerce: Send payment links in DMs with auto-authentication
- WhatsApp Business: Share subscription links without customer login
- Telegram Bots: Offer subscriptions with one-click checkout
- Custom Platforms: Integrate MartianPay subscriptions into your existing user system
Subscription Creation Flow
After successful payment:
- Payment intent status changes to
CompletedorConfirmed - A new subscription is automatically created with
status: active - If payment fails or is incomplete, subscription status will be
incomplete - You'll receive a
subscription.createdwebhook event - The subscription begins its billing cycle based on the selling plan configuration
Subscription Components
Each subscription contains:
- Customer Information: Customer ID, name, and email for the subscriber
- Product Details: Product and variant information including images and descriptions
- Selling Plan: The subscription plan defining billing frequency and pricing
- Billing Cycle: Current cycle number, period start/end, and billing anchor
- Pricing Information: Current and upcoming pricing tiers with discounts
- Payment Method: Default payment method for recurring charges
- Trial Period: Optional trial start and end dates
- Metadata: Custom key-value pairs for additional information
Billing and Pricing
Subscriptions support complex pricing scenarios:
- Trial Periods: Optional free or discounted trial before regular billing starts
- Pricing Tiers: Different pricing for different billing cycles (e.g., first month discount)
- Billing Cycles: Monthly, quarterly, yearly, or custom intervals
- Discounts: Percentage or fixed amount discounts applied to base price
- Current vs Upcoming Pricing: Track current cycle price and next cycle price
2. List Subscriptions
Retrieves a paginated list of all subscriptions for your merchant account. Supports filtering by customer and status.
For detailed API reference, see: List Subscriptions API
Request:
curl --location --request GET 'https://api.martianpay.com/v1/subscriptions?offset=0&limit=20' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Request with Filters:
curl --location --request GET 'https://api.martianpay.com/v1/subscriptions?customer_id=cus_abc123&status=active&offset=0&limit=20' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Query Parameters:
customer_id(optional): Filter subscriptions by specific customer IDstatus(optional): Filter by status (incomplete, active, paused, past_due, canceled)external_id(optional): Filter by external ID for linking to your systemoffset(optional): Pagination offset (default: 0)limit(optional): Number of items per page (default: 20, max: 100)
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"offset": 0,
"limit": 20,
"total": 45,
"data": [
{
"id": "sub_abc123xyz",
"merchant_id": "mer_def456",
"merchant_name": "My Store",
"customer_id": "cus_789ghi",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"product_id": "prod_jkl012",
"product_name": "Premium Monthly Box",
"product_description": "Curated monthly subscription box",
"product_image_url": "https://cdn.example.com/product.jpg",
"variant_id": "var_mno345",
"variant_title": "Standard / Monthly",
"variant_option_values": {
"Size": "Standard",
"Frequency": "Monthly"
},
"variant_price": "49.99",
"selling_plan_id": "sp_pqr678",
"selling_plan_name": "Monthly Subscription",
"selling_plan_description": "Billed monthly, cancel anytime",
"selling_plan_pricing": {
"selling_plan_id": "sp_pqr678",
"selling_plan_name": "Monthly Subscription",
"billing_cycle": "month",
"currency": "USD",
"trial_period_days": 7,
"pricing_tiers": [
{
"policy_type": "FIXED",
"after_cycle": 1,
"cycle_description": "Cycle 1",
"total_cycles": 1,
"base_price": "49.99",
"selling_plan_discount": "10.00",
"subtotal_before_policy": "49.99",
"subtotal_after_policy": "39.99"
},
{
"policy_type": "RECURRING",
"after_cycle": 2,
"cycle_description": "Cycle 2 onwards",
"total_cycles": 0,
"base_price": "49.99",
"selling_plan_discount": "5.00",
"subtotal_before_policy": "49.99",
"subtotal_after_policy": "44.99"
}
]
},
"status": "active",
"collection_method": "charge_automatically",
"default_payment_method_id": "pm_stu901",
"default_payment_method_type": "card",
"default_provider_type": "stripe",
"payment_method_brand": "visa",
"payment_method_last4": "4242",
"external_id": "ext_vwx234",
"metadata": {
"source": "website",
"campaign": "summer2024"
},
"created_at": 1704067200,
"updated_at": 1704067200,
"billing_cycle_anchor": 1704067200,
"current_period_start": 1704067200,
"current_period_end": 1706745600,
"trial_start": 1704067200,
"trial_end": 1704672000,
"canceled_at": null,
"cancel_reason": null,
"cancel_at_period_end": false,
"paused_at": null,
"pause_collection_behavior": null,
"resumes_at": null,
"latest_invoice_id": "inv_yza567",
"current_cycle_number": 1,
"current_pricing_tier": {
"policy_type": "FIXED",
"cycle_number": 1,
"cycle_description": "Cycle 1",
"billing_cycle": "month",
"billing_cycle_interval": 1,
"currency": "USD",
"base_price": "49.99",
"selling_plan_discount": "10.00",
"discount_percentage": "20",
"final_price": "39.99"
},
"upcoming_pricing_tier": {
"policy_type": "RECURRING",
"cycle_number": 2,
"cycle_description": "Cycle 2 onwards",
"billing_cycle": "month",
"billing_cycle_interval": 1,
"currency": "USD",
"base_price": "49.99",
"selling_plan_discount": "5.00",
"discount_percentage": "10",
"final_price": "44.99"
},
"next_charge_amount": "44.99",
"next_charge_amount_display": "$44.99",
"payment_required": false,
"payment_url": null,
"payment_expires_at": null,
"hours_since_creation": 240
}
]
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Subscriptions retrieved successfully |
error_code | Other value | ❌ Error occurred |
Key Fields:
- total: Total number of subscriptions matching your query
- offset: Current offset position
- limit: Number of items per page
- data: Array of subscription objects
Subscription Status Summary:
| Status | Meaning | Action |
|---|---|---|
incomplete | ⏳ Awaiting first payment | Send payment reminder to customer |
active | ✅ Active and billing normally | No action needed |
paused | ⏸️ Temporarily paused | Can resume when ready |
past_due | ⚠️ Payment failed | Update payment method or retry |
canceled | ❌ Canceled, no future charges | No action possible |
Pagination:
- Use
offsetandlimitto navigate through results - If
total>limit, there are more pages available - Next page: increase
offsetbylimitvalue
Filtering:
- Filter by
customer_idto see all subscriptions for a customer - Filter by
statusto find subscriptions in specific states - Filter by
external_idto link to your order system - Combine filters to narrow results
Incomplete Subscriptions:
If payment_required: true:
- Customer needs to complete payment via
payment_url - Check
payment_expires_atfor expiration time - Use
hours_since_creationto prioritize follow-ups
Next Steps:
- Loop through
dataarray to process each subscription - Monitor
statusfield to track subscription lifecycle - Use subscription
idfor cancel, pause, or resume operations - Display
next_charge_amountto show upcoming billing
Important Notes:
-
Pagination: Use
offsetandlimitto navigate through large subscription lists. Thetotalfield shows the total number of subscriptions matching the filter. -
Filtering:
- Filter by
customer_idto see all subscriptions for a specific customer - Filter by
statusto find subscriptions in specific states (e.g., all paused subscriptions) - Combine filters to narrow results (e.g., active subscriptions for a customer)
- Filter by
-
Display Information: The response includes denormalized data for easy display:
- Customer name and email
- Product name, description, and image URL
- Variant title and option values
- Payment method brand and last 4 digits
- Merchant information
-
Pricing Information:
current_pricing_tiershows pricing for the current billing cycleupcoming_pricing_tiershows pricing that will apply in the next cyclenext_charge_amountshows the expected amount for the next payment
-
Incomplete Subscriptions: Subscriptions with
status: incompletehave:payment_required: truepayment_urlfor customer to complete paymentpayment_expires_attimestamp for expirationhours_since_creationto track how long it's been pending
3. Get Subscription
Retrieves detailed information about a specific subscription, including complete billing history, pricing tiers, and payment method details.
For detailed API reference, see: Get Subscription API
Request:
curl --location --request GET 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "sub_abc123xyz",
"merchant_id": "mer_def456",
"merchant_name": "My Store",
"customer_id": "cus_789ghi",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"product_id": "prod_jkl012",
"product_name": "Premium Monthly Box",
"product_description": "Curated monthly subscription box",
"product_image_url": "https://cdn.example.com/product.jpg",
"variant_id": "var_mno345",
"variant_title": "Standard / Monthly",
"variant_option_values": {
"Size": "Standard",
"Frequency": "Monthly"
},
"variant_price": "49.99",
"selling_plan_id": "sp_pqr678",
"selling_plan_name": "Monthly Subscription",
"selling_plan_description": "Billed monthly, cancel anytime",
"selling_plan_pricing": {
"selling_plan_id": "sp_pqr678",
"selling_plan_name": "Monthly Subscription",
"billing_cycle": "month",
"currency": "USD",
"trial_period_days": 7,
"pricing_tiers": [
{
"policy_type": "FIXED",
"after_cycle": 1,
"cycle_description": "Cycle 1",
"total_cycles": 1,
"base_price": "49.99",
"selling_plan_discount": "10.00",
"subtotal_before_policy": "49.99",
"subtotal_after_policy": "39.99"
},
{
"policy_type": "RECURRING",
"after_cycle": 2,
"cycle_description": "Cycle 2 onwards",
"total_cycles": 0,
"base_price": "49.99",
"selling_plan_discount": "5.00",
"subtotal_before_policy": "49.99",
"subtotal_after_policy": "44.99"
}
]
},
"status": "active",
"collection_method": "charge_automatically",
"default_payment_method_id": "pm_stu901",
"default_payment_method_type": "card",
"default_provider_type": "stripe",
"payment_method_brand": "visa",
"payment_method_last4": "4242",
"external_id": "ext_vwx234",
"metadata": {
"source": "website",
"campaign": "summer2024"
},
"created_at": 1704067200,
"updated_at": 1704067200,
"billing_cycle_anchor": 1704067200,
"current_period_start": 1704067200,
"current_period_end": 1706745600,
"trial_start": 1704067200,
"trial_end": 1704672000,
"canceled_at": null,
"cancel_reason": null,
"cancel_at_period_end": false,
"paused_at": null,
"pause_collection_behavior": null,
"resumes_at": null,
"latest_invoice_id": "inv_yza567",
"current_cycle_number": 1,
"current_pricing_tier": {
"policy_type": "FIXED",
"cycle_number": 1,
"cycle_description": "Cycle 1",
"billing_cycle": "month",
"billing_cycle_interval": 1,
"currency": "USD",
"base_price": "49.99",
"selling_plan_discount": "10.00",
"discount_percentage": "20",
"final_price": "39.99"
},
"upcoming_pricing_tier": {
"policy_type": "RECURRING",
"cycle_number": 2,
"cycle_description": "Cycle 2 onwards",
"billing_cycle": "month",
"billing_cycle_interval": 1,
"currency": "USD",
"base_price": "49.99",
"selling_plan_discount": "5.00",
"discount_percentage": "10",
"final_price": "44.99"
},
"next_charge_amount": "44.99",
"next_charge_amount_display": "$44.99",
"payment_required": false,
"payment_url": null,
"payment_expires_at": null,
"hours_since_creation": 240
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Subscription retrieved successfully |
error_code | "subscription_not_found" | ❌ No subscription exists with this ID |
error_code | Other value | ❌ Other error occurred |
Key Subscription Information:
| Field | Description |
|---|---|
status | Current subscription status (incomplete, active, paused, past_due, canceled) |
current_cycle_number | Which billing cycle the subscription is in |
current_period_start / current_period_end | Current billing period dates |
next_charge_amount | Expected amount for next payment |
current_pricing_tier | Pricing for current billing cycle |
upcoming_pricing_tier | Pricing that will apply in next cycle |
Pricing Information:
- current_pricing_tier: Shows pricing for the current billing cycle with discounts applied
- upcoming_pricing_tier: Preview of pricing for next cycle (may differ if tiered pricing changes)
- selling_plan_pricing: Full pricing tier breakdown for all cycles
- trial_period_days: If set, shows trial duration before first charge
Billing Cycle Tracking:
- billing_cycle_anchor: Reference date for billing cycles
- current_period_start / current_period_end: Current billing period
- Use these dates to calculate when next charge will occur
Status Indicators:
| Status | Meaning | What to Do |
|---|---|---|
incomplete | ⏳ Awaiting first payment | Direct customer to payment_url |
active | ✅ Active and billing | Monitor for upcoming charges |
paused | ⏸️ Temporarily paused | Can resume anytime |
past_due | ⚠️ Payment failed | Update payment method |
canceled | ❌ Canceled | Cannot be reactivated |
Trial Period: If subscription has a trial:
- Check
trial_startandtrial_enddates - First charge occurs after trial ends
trial_period_daysshows trial length
Next Steps:
- Display subscription details to customer in your dashboard
- Show
next_charge_amountandcurrent_period_endfor transparency - If
status: incomplete, prompt customer to complete payment - Use
cancel_at_period_endto show if subscription will end soon - Check
paused_atandresumes_atfor paused subscriptions
Important Notes:
-
Complete Subscription Details: The response includes all subscription information including:
- Complete customer, product, and variant details
- Full selling plan pricing tier breakdown
- Current and upcoming billing cycle information
- Payment method details
- Trial period dates if applicable
-
Pricing Tiers: The
selling_plan_pricing.pricing_tiersarray shows:FIXEDtiers: Apply to specific cycles (e.g., first month discount)RECURRINGtiers: Apply to ongoing cycles (e.g., cycle 2 onwards)- Discount amounts and percentages
- Subtotals before and after discounts
-
Billing Cycle Information:
billing_cycle_anchor: The reference date for billing cyclescurrent_period_startandcurrent_period_end: Current billing periodcurrent_cycle_number: Which cycle the subscription is currently in- Use these to understand when the next charge will occur
-
Trial Periods: If the subscription has a trial:
trial_startandtrial_endshow trial period datestrial_period_daysin selling_plan_pricing shows trial length- First charge occurs after trial ends
-
Status Indicators: Check these fields to understand subscription state:
status: Current subscription statuscancel_at_period_end: If true, subscription ends at period endpaused_at: Timestamp when subscription was pausedresumes_at: Timestamp when paused subscription will auto-resume
4. Cancel Subscription
Cancels a subscription either immediately or at the end of the current billing period. Canceled subscriptions cannot be reactivated.
For detailed API reference, see: Cancel Subscription API
Request (Cancel at Period End - Default):
curl --location --request POST 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz/cancel' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"cancel_at_period_end": true,
"cancel_reason": "Customer requested cancellation"
}'
Request (Cancel Immediately):
curl --location --request POST 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz/cancel' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"cancel_at_period_end": false,
"cancel_reason": "Payment failure"
}'
Request Body Parameters:
cancel_at_period_end(optional):true: Cancel at the end of current billing period (default)false: Cancel immediately
cancel_reason(optional): Reason for cancellation (stored for analytics)
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "sub_abc123xyz",
"merchant_id": "mer_def456",
"merchant_name": "My Store",
"customer_id": "cus_789ghi",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"product_id": "prod_jkl012",
"product_name": "Premium Monthly Box",
"product_description": "Curated monthly subscription box",
"product_image_url": "https://cdn.example.com/product.jpg",
"variant_id": "var_mno345",
"variant_title": "Standard / Monthly",
"status": "active",
"collection_method": "charge_automatically",
"default_payment_method_id": "pm_stu901",
"canceled_at": 1706745600,
"cancel_reason": "Customer requested cancellation",
"cancel_at_period_end": true,
"current_period_start": 1704067200,
"current_period_end": 1706745600,
"billing_cycle_anchor": 1704067200,
"metadata": {},
"created_at": 1704067200,
"updated_at": 1706745600
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Subscription canceled successfully |
error_code | "subscription_not_found" | ❌ No subscription exists with this ID |
error_code | "subscription_already_canceled" | ❌ Subscription is already canceled |
error_code | Other value | ❌ Other error occurred |
Key Cancellation Fields:
| Field | Description |
|---|---|
status | Remains active if cancel_at_period_end=true, changes to canceled if false |
canceled_at | Timestamp when cancellation was initiated |
cancel_reason | Reason provided for cancellation (stored for analytics) |
cancel_at_period_end | If true, subscription ends at period end; if false, ends immediately |
current_period_end | When subscription will actually end (if cancel_at_period_end=true) |
Cancellation Behavior:
| cancel_at_period_end | Immediate Effect | End Result |
|---|---|---|
true (recommended) | Stays active until period end | Customer keeps access, no future charges after period ends |
false | Changes to canceled immediately | Customer loses access now, no automatic refund issued |
What Happens After Cancellation:
If cancel_at_period_end: true:
- ✅ Subscription remains active until
current_period_end - ✅ Customer can continue using service
- ✅ No more charges after period ends
- ✅ Status changes to
canceledautomatically at period end
If cancel_at_period_end: false:
- ❌ Subscription becomes
canceledimmediately - ❌ Customer loses access immediately
- ⚠️ No automatic refund (issue manually if needed)
- ⚠️ Use for fraud, violations, or customer-requested immediate cancellation
Important Considerations:
- Cannot Be Reactivated: Once canceled, subscription cannot be resumed
- Historical Record: Canceled subscriptions remain in history for record-keeping
- Customer Communication: Send confirmation email with cancellation details
- Refunds: Handle refunds separately using Refund API if needed
Next Steps:
- Send cancellation confirmation to customer
- If
cancel_at_period_end: true, remind customer they have access untilcurrent_period_end - If
cancel_at_period_end: false, confirm immediate cancellation - Use
cancel_reasonfor analytics to reduce churn - Consider offering retention incentives before final cancellation
Important Notes:
-
Cancel at Period End (Recommended):
- Set
cancel_at_period_end: trueto let customer use service until period ends - Subscription remains
status: activeuntil the period end date - No more charges after the current period ends
- Customer gets full value for their last payment
- After period ends, status changes to
canceled
- Set
-
Cancel Immediately:
- Set
cancel_at_period_end: falsefor immediate cancellation - Subscription status changes to
canceledimmediately - No refund is automatically issued (you must issue refund separately if needed)
- Customer loses access immediately
- Use this for fraud, payment failures, or customer-requested immediate cancellation
- Set
-
Cancellation Fields: After cancellation:
canceled_at: Timestamp when cancellation was initiatedcancel_reason: Reason provided (useful for analytics and reporting)cancel_at_period_end: Indicates if cancellation is deferredstatus: Changes tocanceledimmediately (if cancel_at_period_end=false) or after period ends
-
Cannot Be Reactivated: Once canceled, a subscription cannot be resumed or reactivated. The customer would need to create a new subscription.
-
Invoices:
- If
cancel_at_period_end: true, the current invoice remains valid - If
cancel_at_period_end: false, pending invoices may be voided - Check
latest_invoice_idfor invoice status
- If
-
Best Practice: Use
cancel_at_period_end: trueby default to provide better customer experience and avoid refund complications.
5. Pause Subscription
Temporarily pauses a subscription, preventing new charges from being created. Useful for seasonal businesses or when customers want to temporarily suspend service.
For detailed API reference, see: Pause Subscription API
Request:
curl --location --request POST 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz/pause' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"behavior": "void",
"resumes_at": 1709424000
}'
Request (Pause Indefinitely):
curl --location --request POST 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz/pause' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"behavior": "keep_as_draft"
}'
Request Body Parameters:
behavior(required): How to handle pending invoicesvoid: Cancel/void any pending invoices (recommended)keep_as_draft: Keep pending invoices as draftmark_uncollectible: Mark pending invoices as uncollectiblefree: Continue creating invoices but mark them as free
resumes_at(optional): Unix timestamp for automatic resumption
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "sub_abc123xyz",
"merchant_id": "mer_def456",
"merchant_name": "My Store",
"customer_id": "cus_789ghi",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"product_id": "prod_jkl012",
"product_name": "Premium Monthly Box",
"product_description": "Curated monthly subscription box",
"status": "paused",
"collection_method": "charge_automatically",
"default_payment_method_id": "pm_stu901",
"paused_at": 1706745600,
"pause_collection_behavior": "void",
"resumes_at": 1709424000,
"cancel_at_period_end": false,
"current_period_start": 1704067200,
"current_period_end": 1706745600,
"metadata": {},
"created_at": 1704067200,
"updated_at": 1706745600
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Subscription paused successfully |
error_code | "subscription_not_found" | ❌ No subscription exists with this ID |
error_code | "subscription_already_paused" | ❌ Subscription is already paused |
error_code | Other value | ❌ Other error occurred |
Key Pause Fields:
| Field | Description |
|---|---|
status | Changed to paused |
paused_at | Timestamp when pause was initiated |
pause_collection_behavior | How pending invoices are handled (void, keep_as_draft, mark_uncollectible, free) |
resumes_at | Timestamp for automatic resumption (null if indefinite) |
Pause Behaviors Explained:
| Behavior | What It Does | When to Use |
|---|---|---|
void ✅ (Recommended) | Cancels pending invoices, no charges | Customer doesn't want any billing during pause |
keep_as_draft | Keeps invoices as drafts, can finalize later | Want to manually review before resuming |
mark_uncollectible | Marks invoices as uncollectible | For accounting/reporting purposes |
free | Creates $0 invoices during pause | Track billing cycles with free service |
Auto-Resume:
- If
resumes_atis set: Subscription will automatically resume at that timestamp - If
resumes_atis null: Paused indefinitely until manually resumed - Use auto-resume for seasonal businesses or temporary suspensions
What Happens During Pause:
- ⏸️ Status changes to
pausedimmediately - 🛑 No new charges during pause period (unless behavior=free)
- 📅 Billing cycle may be extended based on pause duration
- 🔄 Can be resumed anytime with Resume API
Customer Access: You control whether customers retain access during pause:
- Define your own business rules for paused subscriptions
- Some businesses give limited access, others revoke completely
- Document your pause policy clearly to customers
Next Steps:
- Notify customer that subscription is paused
- Explain pause behavior and when charges will resume
- If
resumes_atis set, inform customer of auto-resume date - Consider offering alternative plans if customer wants to remain active
- Track pause reasons for analytics to improve retention
Important Notes:
-
Pause Behaviors:
-
void(Recommended): Cancels pending invoices, no charges during pause- Clean and simple
- Customer not charged during pause period
- Use when customer doesn't want any billing activity
-
keep_as_draft: Keeps invoices as drafts, can be finalized later- Invoices created but not sent or charged
- Useful if you want to manually review before resuming
- Drafts can be voided or finalized when resuming
-
mark_uncollectible: Marks invoices as uncollectible- Indicates invoices won't be collected
- Useful for accounting/reporting purposes
- Cleaner than void for some accounting systems
-
free: Creates invoices but marks them as $0- Maintains billing cycle tracking
- Shows free service periods in billing history
- Useful when giving free service during pause
-
-
Auto-Resume:
- Set
resumes_at(Unix timestamp) to automatically resume subscription - Subscription status changes from
pausedtoactiveat that time - Billing resumes according to original schedule
- Omit
resumes_atto pause indefinitely (manual resume required)
- Set
-
Pause Period:
- Subscription status changes to
pausedimmediately paused_attimestamp records when pause startedpause_collection_behaviorshows which behavior is active- No new invoices created during pause (unless behavior=free)
- Subscription status changes to
-
Billing Cycle:
- Current period end date may be extended based on pause duration
- Billing cycle anchor may shift when resumed
- Check subscription after resume to see updated billing dates
-
Customer Access: Define your own rules for customer access during pause:
- You control whether customer retains access to service
- Some businesses give limited access, others revoke completely
- Document your pause policy clearly to customers
-
Multiple Pauses: You can pause and resume a subscription multiple times.
6. Resume Subscription
Resumes a paused subscription, reactivating billing according to the original schedule.
For detailed API reference, see: Resume Subscription API
Request:
curl --location --request POST 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz/resume' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}'
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "sub_abc123xyz",
"merchant_id": "mer_def456",
"merchant_name": "My Store",
"customer_id": "cus_789ghi",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"product_id": "prod_jkl012",
"product_name": "Premium Monthly Box",
"product_description": "Curated monthly subscription box",
"status": "active",
"collection_method": "charge_automatically",
"default_payment_method_id": "pm_stu901",
"paused_at": null,
"pause_collection_behavior": null,
"resumes_at": null,
"cancel_at_period_end": false,
"current_period_start": 1706745600,
"current_period_end": 1709424000,
"billing_cycle_anchor": 1704067200,
"metadata": {},
"created_at": 1704067200,
"updated_at": 1709337600
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Subscription resumed successfully |
error_code | "subscription_not_found" | ❌ No subscription exists with this ID |
error_code | "subscription_not_paused" | ❌ Subscription is not currently paused |
error_code | Other value | ❌ Other error occurred |
Key Resume Fields:
| Field | Before Resume | After Resume |
|---|---|---|
status | paused | active |
paused_at | Pause timestamp | null (cleared) |
pause_collection_behavior | Pause behavior | null (cleared) |
resumes_at | Auto-resume time or null | null (cleared) |
current_period_start | Old period | New period starts from resume date |
current_period_end | Old period | New period ends based on billing cycle |
What Happens on Resume:
- ✅ Status changes from
pausedtoactive - ✅ All pause-related fields are cleared
- ✅ New billing period starts from resume date
- ✅ Next invoice will be generated based on selling plan schedule
- ✅ Automatic billing resumes according to original schedule
Billing After Resume:
- New Period Dates:
current_period_startandcurrent_period_endare updated - Next Charge: Will occur at the end of the new period
- Billing Cycle: Continues according to selling plan (monthly, yearly, etc.)
- Pricing: Uses the selling plan pricing tier for current cycle number
Draft Invoices (if pause behavior was keep_as_draft):
- Review any draft invoices from the pause period
- Manually decide whether to finalize, void, or modify them
- Draft invoices are NOT automatically processed on resume
- Handle them separately through invoice management
Auto-Resume vs Manual Resume:
- If subscription had
resumes_atset, system auto-resumes at that time - You can manually resume before the auto-resume time
- Manual resume clears the
resumes_attimestamp - Either method results in the same active state
Payment Method Check: Ensure payment method is still valid before resuming, especially if:
- Subscription was paused for a long time
- Credit card may have expired
- Customer may have changed payment preferences
Next Steps:
- Send confirmation email to customer about resumed subscription
- Show next billing date and amount for transparency
- If customer forgot about subscription, give them option to cancel
- Verify payment method is up to date to avoid payment failures
- Monitor first payment after resume for any issues
Important Notes:
-
Status Change:
- Subscription status changes from
pausedtoactive - Pause-related fields are cleared:
paused_at,pause_collection_behavior,resumes_at - Normal billing cycle resumes
- Subscription status changes from
-
Billing Resumption:
- New billing period starts from resume date
current_period_startandcurrent_period_endare updated- Next invoice will be generated based on new period dates
- Billing occurs according to the selling plan schedule
-
Draft Invoices: If pause behavior was
keep_as_draft:- Review and manually handle any draft invoices
- Decide whether to finalize, void, or modify them
- Draft invoices are not automatically processed on resume
-
Auto-Resume: If subscription was set to auto-resume with
resumes_at:- System automatically resumes at the specified timestamp
- You can also manually resume before the auto-resume time
- Manual resume clears the
resumes_attimestamp
-
Customer Notification: Consider notifying customers when their subscription resumes, especially if:
- Resume will trigger immediate payment
- Customer manually requested pause
- Customer may have forgotten about the subscription
-
Payment Method: Ensure the payment method on file is still valid before resuming, especially for long pauses.
7. Update Subscription (Plan Change)
Updates a subscription's plan, allowing customers to upgrade or downgrade their subscription. Supports automatic proration calculation for mid-cycle changes.
For detailed API reference, see: Update Subscription API
Request:
curl --location --request POST 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"primary_variant": {
"selling_plan_id": "sp_newplan456"
},
"proration_behavior": "always_invoice",
"billing_cycle_anchor": "now"
}'
Request Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
primary_variant | object | Yes | The primary variant selection to update |
primary_variant.selling_plan_id | string | Yes | The new selling plan ID to switch to |
primary_variant.variant_id | string | No | The new variant ID (optional, keeps current if not specified) |
primary_variant.quantity | integer | No | Quantity (default: 1) |
proration_behavior | string | No | How proration is handled (see detailed explanation below) |
proration_date | integer | No | Unix timestamp for custom proration calculation (see detailed explanation below) |
billing_cycle_anchor | string | No | Controls billing cycle timing (see detailed explanation below) |
metadata | object | No | Key-value pairs for additional information |
addons | array | No | Addon variant selections (reserved for future use) |
Parameter Details
proration_behavior
Controls how proration charges are handled during plan changes.
Values:
"always_invoice": Create an invoice immediately for the prorated amount. Customer is charged right away."create_prorations": Defer the plan change to next billing cycle (used for deferred upgrades)."none": No proration calculation. Only valid for downgrades.
Default behavior (when not specified):
- Upgrades:
"always_invoice"(immediate charge with credit for unused time on old plan) - Downgrades:
"none"(no charge, change takes effect at period end)
VALID COMBINATIONS (other combinations return error invalid_proration_config):
For UPGRADES (3 valid combinations):
| proration_behavior | billing_cycle_anchor | Result |
|---|---|---|
| always_invoice | now | Immediate upgrade, charge now, reset cycle |
| always_invoice | unchanged | Immediate upgrade, charge now, keep cycle |
| create_prorations | unchanged | Deferred upgrade, change at period end |
For DOWNGRADES (1 valid combination):
| proration_behavior | billing_cycle_anchor | Result |
|---|---|---|
| none | unchanged | Deferred downgrade, change at period end |
INVALID COMBINATIONS (return error):
create_prorations+now: Would reset cycle without settling, customer credit lostnone+now/unchanged(upgrade): Would give customer free upgrade- any +
now(downgrade): Downgrade cannot take effect immediately
billing_cycle_anchor
Controls when the new billing cycle starts after a plan change.
Values:
"now": Reset billing cycle immediately. New period starts from the change time."unchanged": Keep current billing cycle anchor.
Default behavior (when not specified):
- Upgrades:
"now"(billing cycle resets to start fresh) - Downgrades: Must be
"unchanged"(downgrades always take effect at period end)
Note: For downgrades, only
"unchanged"is valid. Using"now"will return an error. Seeproration_behaviordocumentation for valid combinations.
proration_date
A Unix timestamp (seconds) for custom proration calculation (backdating). When provided, proration credits are calculated as if the plan change happened at this time instead of now.
How it works:
- The remaining time on the old plan is calculated from
proration_datetocurrent_period_end - Credit = (old_plan_price) × (remaining_time / total_period_time)
- This allows backdating: if a customer requested a change yesterday, you can use yesterday's timestamp
Constraints:
- Must be within the current billing period (between
current_period_startandcurrent_period_end) - If not specified, current time (now) is used for calculation
Example: If period is Dec 1-31 and proration_date is Dec 15, customer gets credit for 16 remaining days.
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "sub_abc123xyz",
"merchant_id": "mer_def456",
"customer_id": "cus_789ghi",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"product_id": "prod_jkl012",
"product_name": "Premium Monthly Box",
"variant_id": "var_mno345",
"variant_title": "Premium / Monthly",
"variant_price": "99.99",
"selling_plan_id": "sp_newplan456",
"selling_plan_name": "Premium Monthly Subscription",
"status": "active",
"collection_method": "charge_automatically",
"default_payment_method_id": "pm_stu901",
"created_at": 1704067200,
"updated_at": 1706745600,
"billing_cycle_anchor": 1706745600,
"current_period_start": 1706745600,
"current_period_end": 1709424000,
"current_cycle_number": 3,
"next_charge_amount": "99.99",
"next_charge_amount_display": "$99.99",
"next_charge_date": 1709424000,
"applied": true,
"is_upgrade": true,
"effective_date": 1706745600,
"charge_today": "66.66",
"proration_behavior": "always_invoice",
"proration_date": 1706745600,
"proration_credit": "33.33",
"proration_details": {
"current_price": "4999",
"target_price": "9999",
"days_remaining": 20,
"total_days": 30,
"credited_amount": "3333",
"charged_amount": "6666",
"net_amount": "3333"
},
"pending_update": null,
"metadata": {}
}
}
Understanding the Response
How to Check if the Request Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Subscription updated successfully |
error_code | "subscription_not_found" | ❌ No subscription exists with this ID |
error_code | "subscription_has_open_invoice" | ❌ Cannot update while there's an unpaid invoice |
error_code | "subscription_has_pending_update" | ❌ Cannot update while there's a pending update |
error_code | Other value | ❌ Other error occurred |
Key Update Fields:
| Field | Description |
|---|---|
applied | true indicates the change was applied (vs. preview) |
is_upgrade | true for upgrade, false for downgrade |
effective_date | Unix timestamp when the change took/will take effect |
charge_today | Net amount charged today (formatted string, e.g., "66.66") |
proration_behavior | How proration was handled |
proration_date | Timestamp used for proration calculation |
proration_credit | Credit for unused time on old plan |
proration_details | Detailed breakdown of proration calculation |
pending_update | Contains scheduled changes for downgrades (null for immediate changes) |
Proration Details Object:
| Field | Description |
|---|---|
current_price | Current plan price in cents |
target_price | Target plan price in cents |
days_remaining | Days remaining in current billing period |
total_days | Total days in the billing period |
credited_amount | Credit for unused time (in cents) |
charged_amount | Charge for new plan's remaining time (in cents) |
net_amount | Final net amount: charged - credited (in cents) |
Upgrade vs Downgrade Behavior
Upgrades (new price > current price):
- ✅ Applied immediately
- ✅ Customer charged prorated difference today
- ✅ New plan takes effect immediately
- ✅ Billing cycle can be reset (
billing_cycle_anchor: "now")
Downgrades (new price < current price):
- ⏳ Scheduled as pending update
- ⏳ Takes effect at end of current billing period
- ⏳ Customer continues on current plan until
effective_date - ⏳
pending_updateobject contains scheduled change details
Example Downgrade Response with Pending Update:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "sub_abc123xyz",
"status": "active",
"selling_plan_id": "sp_currentplan123",
"selling_plan_name": "Premium Monthly",
"applied": false,
"is_upgrade": false,
"effective_date": 1709424000,
"pending_update": {
"target_selling_plan_id": "sp_basicplan789",
"target_selling_plan_name": "Basic Monthly",
"target_variant_id": "var_basic123",
"target_variant_title": "Basic / Monthly",
"target_variant_price": "$29.99",
"change_type": "downgrade",
"effective_date": 1709424000,
"scheduled_at": 1706745600,
"proration_behavior": "none",
"next_charge_amount": "29.99",
"billing_cycle_anchor": "unchanged"
},
"current_period_end": 1709424000
}
}
Pending Update Object
When a downgrade is scheduled, the pending_update object contains:
| Field | Description |
|---|---|
target_selling_plan_id | The selling plan to change to |
target_selling_plan_name | Name of the target selling plan |
target_variant_id | The variant to change to (if specified) |
target_variant_title | Display title of the target variant |
target_variant_price | Base price of the target variant (formatted) |
target_variant_option_values | Option values of the target variant |
change_type | "upgrade" or "downgrade" |
effective_date | Unix timestamp when the change will take effect |
scheduled_at | Unix timestamp when the update was scheduled |
proration_behavior | How proration will be handled |
proration_date | Timestamp for proration calculation (optional) |
next_charge_amount | Amount to be charged after change takes effect |
billing_cycle_anchor | Billing cycle timing setting |
metadata | Additional information for this pending update |
Restrictions
The update API will return errors in the following cases:
-
Open Invoice: Cannot update if there's an unpaid invoice
- Error:
subscription_has_open_invoice - Solution: Wait for invoice to be paid or void it first
- Error:
-
Pending Update: Cannot update if there's already a pending update
- Error:
subscription_has_pending_update - Solution: Cancel the pending update first or wait for it to apply
- Error:
-
Invalid Status: Cannot update canceled or incomplete subscriptions
- Error:
subscription_invalid_status - Solution: Subscription must be active or paused
- Error:
Important Notes:
-
Proration Calculation: The system automatically calculates:
- Credit for unused days on current plan
- Charge for remaining days on new plan
- Net amount = Charge - Credit
-
Billing Cycle Anchor:
"now": Resets billing cycle to start today (useful for upgrades)"unchanged": Keeps current billing cycle dates (default for downgrades)
-
Immediate vs Deferred:
- Upgrades are applied immediately by default
- Downgrades are scheduled for period end by default
- This prevents giving customers "free" upgrades by downgrading mid-cycle
-
Invoice Generation:
- For upgrades with
always_invoice: A proration invoice is created and charged immediately - For downgrades: No immediate charge; next invoice reflects new price
- For upgrades with
-
Webhooks: Listen for these events after update:
subscription.updated: Subscription details changedinvoice.created: Proration invoice created (for upgrades)invoice.paid: Proration invoice paid
8. Preview Subscription Update
Preview the proration calculation before actually updating a subscription plan. This allows you to show customers what they'll be charged before confirming the change.
For detailed API reference, see: Preview Subscription Update API
Request:
curl --location --request POST 'https://api.martianpay.com/v1/subscriptions/sub_abc123xyz/preview' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data-raw '{
"primary_variant": {
"selling_plan_id": "sp_newplan456"
},
"proration_behavior": "always_invoice",
"billing_cycle_anchor": "now"
}'
Request Body Parameters:
Same as Update Subscription API.
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"id": "sub_abc123xyz",
"merchant_id": "mer_def456",
"customer_id": "cus_789ghi",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"product_id": "prod_jkl012",
"product_name": "Premium Monthly Box",
"variant_id": "var_mno345",
"variant_title": "Standard / Monthly",
"variant_price": "49.99",
"selling_plan_id": "sp_pqr678",
"selling_plan_name": "Monthly Subscription",
"status": "active",
"applied": false,
"is_upgrade": true,
"effective_date": 1706745600,
"charge_today": "66.66",
"proration_behavior": "always_invoice",
"proration_date": 1706745600,
"proration_credit": "33.33",
"proration_details": {
"current_price": "4999",
"target_price": "9999",
"days_remaining": 20,
"total_days": 30,
"credited_amount": "3333",
"charged_amount": "6666",
"net_amount": "3333"
},
"next_charge_amount": "99.99",
"next_charge_amount_display": "$99.99",
"next_charge_date": 1709424000
}
}
Understanding the Response
Key Difference from Update API:
appliedisfalse- indicates this is a preview, no changes were made
How to Check if Preview Was Successful:
| Field | Value | Meaning |
|---|---|---|
error_code | "success" | ✅ Preview calculated successfully |
error_code | "subscription_not_found" | ❌ No subscription exists with this ID |
error_code | Other value | ❌ Other error occurred |
Key Preview Fields:
| Field | Description |
|---|---|
applied | Always false for preview (no changes made) |
is_upgrade | true if new plan is more expensive, false for downgrade |
charge_today | Amount that would be charged today (for upgrades) |
effective_date | When the change would take effect |
proration_details | Detailed breakdown of proration calculation |
next_charge_amount | What the next regular charge would be |
next_charge_date | When the next regular charge would occur |
Use Cases
1. Display Plan Change Confirmation:
// Frontend example
const preview = await fetch('/api/subscriptions/sub_abc123/preview', {
method: 'POST',
body: JSON.stringify({
primary_variant: { selling_plan_id: 'sp_premium' }
})
});
const data = await preview.json();
// Display to customer
if (data.is_upgrade) {
showMessage(`
Upgrade to ${data.selling_plan_name}
Charge today: $${data.charge_today}
Credit for unused time: $${data.proration_credit}
Next billing date: ${formatDate(data.next_charge_date)}
Next charge: ${data.next_charge_amount_display}
`);
} else {
showMessage(`
Downgrade to ${data.selling_plan_name}
Effective: ${formatDate(data.effective_date)}
You'll continue on your current plan until then.
New price after change: ${data.next_charge_amount_display}
`);
}
2. Compare Multiple Plans:
// Preview multiple plan options
const plans = ['sp_basic', 'sp_pro', 'sp_enterprise'];
const previews = await Promise.all(
plans.map(planId =>
previewPlanChange(subscriptionId, planId)
)
);
// Display comparison table
displayPlanComparison(previews);
Important Notes:
-
No Side Effects: Preview does not:
- Modify the subscription
- Create invoices
- Charge the customer
- Send any webhooks
-
Real-Time Calculation: Preview uses current date/time for proration, so results may vary if customer waits before confirming
-
Same Validation: Preview applies the same validation as update (e.g., checks for open invoices)
-
Use Before Update: Always preview before calling the update API to ensure customers understand what they'll be charged
9. Customer Portal for Subscription Management
The Customer Portal allows your customers to self-service their subscription management, including viewing, pausing, resuming, and canceling subscriptions. You can use ephemeral tokens to provide secure, seamless access to the portal without requiring additional login.
Using Ephemeral Tokens for Portal Access
Generate an ephemeral token and append it to the portal URL to authenticate customers automatically:
Step 1: Generate Ephemeral Token
curl --location --request POST 'https://api.martianpay.com/v1/customers/ephemeral_tokens' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {BASE64_ENCODE(api_key + ":")}' \
--data '{
"idp_key": "email",
"idp_subject": "customer@example.com",
"provider": "your_platform",
"allow_create": false,
"issued_by": "your_backend_service"
}'
Response:
{
"error_code": "success",
"msg": "success",
"data": {
"token": "eph_abc123xyz789",
"expires_at": 1735689600
}
}
Step 2: Direct Customer to Portal
Construct the portal URL with the ephemeral token and your merchant ID:
https://buy.martianpay.com/portal?merchant_id=accu_M7PTgveSgMtTtPHbjFgEtAlD&ephemeral_token=eph_abc123xyz789
URL Parameters:
merchant_id: Your merchant account ID (starts withaccu_)ephemeral_token: The token generated from the API
Portal Features
Once authenticated, customers can:
- View all subscriptions: See active, paused, and canceled subscriptions
- View subscription details: Check billing dates, pricing, and product information
- Pause subscriptions: Temporarily pause active subscriptions
- Resume subscriptions: Reactivate paused subscriptions
- Cancel subscriptions: Cancel unwanted subscriptions
- Update payment methods: Change card or payment information
- View billing history: Access past invoices and receipts
Use Cases
Email Integration:
Hi {customer_name},
Manage your subscriptions here:
https://buy.martianpay.com/portal?merchant_id=accu_M7PTgveSgMtTtPHbjFgEtAlD&ephemeral_token={generated_token}
This link expires in 15 minutes.
In-App/Website Button:
// Generate token on your backend
const response = await fetch('/api/generate-portal-token', {
method: 'POST',
body: JSON.stringify({ customer_email: user.email })
});
const { token } = await response.json();
// Redirect user to portal
const portalUrl = `https://buy.martianpay.com/portal?merchant_id=${MERCHANT_ID}&ephemeral_token=${token}`;
window.location.href = portalUrl;
Webhook-Triggered Access: When a subscription needs attention (e.g., payment failed), automatically send customers a portal link:
Your payment failed. Please update your payment method:
https://buy.martianpay.com/portal?merchant_id=accu_M7PTgveSgMtTtPHbjFgEtAlD&ephemeral_token={generated_token}
Security Considerations
- Short-lived tokens: Ephemeral tokens typically expire in 5-15 minutes
- Single-use recommended: Generate a new token for each portal access
- Customer verification: Tokens are tied to specific customer identities (email, phone, or UUID)
- HTTPS only: Portal URLs must always use HTTPS
- Backend generation: Never generate tokens in frontend code - always use your secure backend
Alternative Authentication Methods
If you prefer not to use ephemeral tokens, the portal also supports:
- Email + OTP: Customers enter their email and receive a one-time code
- Auth tokens: Long-lived tokens for authenticated sessions
10. Webhook Events for Subscriptions
MartianPay sends webhook notifications for subscription and invoice events, enabling you to track subscription lifecycle and billing in real-time.
10.1 Subscription Events
| Event Type | Description | Triggered When |
|---|---|---|
subscription.created | Subscription created | A new subscription is successfully created |
subscription.updated | Subscription details updated | Subscription details change (e.g., status, payment method) |
subscription.deleted | Subscription deleted | Subscription is permanently deleted |
subscription.paused | Subscription paused | Subscription billing is paused |
subscription.resumed | Subscription resumed | Paused subscription is reactivated |
subscription.trial_will_end | Trial ending soon | Sent 3 days before trial period ends |
10.2 Invoice Events
Subscriptions generate invoices for each billing cycle. Monitor these events to track billing:
| Event Type | Description | Triggered When |
|---|---|---|
invoice.created | Invoice created | New invoice generated for billing cycle |
invoice.finalized | Invoice finalized | Invoice finalized and ready for payment |
invoice.paid | Invoice paid successfully | Payment for invoice completed |
invoice.payment_succeeded | Invoice payment succeeded | Payment successfully processed (same as paid) |
invoice.payment_failed | Invoice payment failed | Payment attempt failed |
invoice.payment_action_required | Payment action needed | Additional customer action required (e.g., 3D Secure) |
invoice.marked_uncollectible | Invoice uncollectible | Marked as uncollectible after retries |
invoice.voided | Invoice voided | Invoice canceled and voided |
10.3 Webhook Payload Example
Subscription Event:
{
"id": "evt_056qw9fr9PndljykFUqSIf6t",
"object": "event",
"api_version": "2025-01-22",
"created": 1748580845,
"data": {
"object": {
"id": "sub_abc123xyz",
"object": "subscription",
"status": "active",
"created": 1748500000,
"updated": 1748580845,
"customer": "cus_7p51tXM7GBi7sz5J",
"current_period_start": 1748500000,
"current_period_end": 1751178400,
"billing_cycle_anchor": 1748500000,
"cancel_at_period_end": false,
"canceled_at": null,
"ended_at": null,
"trial_start": null,
"trial_end": null,
"metadata": {
"customer_tier": "premium",
"source": "web"
}
},
"previous_attributes": {
"status": "trialing",
"updated": 1748580000
}
},
"livemode": false,
"pending_webhooks": 0,
"type": "subscription.updated"
}
Invoice Event:
{
"id": "evt_056qw9fr9PndljykFUqSIf6t",
"object": "event",
"api_version": "2025-01-22",
"created": 1748580845,
"data": {
"object": {
"id": "inv_abc123xyz",
"object": "invoice",
"subscription": "sub_abc123xyz",
"customer": "cus_7p51tXM7GBi7sz5J",
"amount_due": "49.99",
"amount_paid": "49.99",
"currency": "USD",
"status": "paid",
"created": 1748580000,
"due_date": 1748583600,
"period_start": 1748500000,
"period_end": 1751178400,
"billing_reason": "subscription_cycle"
}
},
"livemode": false,
"pending_webhooks": 0,
"type": "invoice.paid"
}
10.4 Webhook Event Flow
Typical Subscription Lifecycle:
subscription.created- New subscription created (status:activeortrialing)subscription.trial_will_end- (if trial) Sent 3 days before trial endsinvoice.created- Invoice generated for billing cycleinvoice.finalized- Invoice ready for paymentinvoice.payment_succeeded- Payment successfulinvoice.paid- Invoice marked as paid- Repeat steps 3-6 for each billing cycle
subscription.paused- (optional) Subscription pausedsubscription.resumed- (optional) Subscription resumedsubscription.deleted- Subscription canceled and deleted
Failed Payment Flow:
invoice.created- Invoice generatedinvoice.finalized- Invoice readyinvoice.payment_failed- Payment attempt failed- Automatic retry with
invoice.payment_failedon each retry invoice.marked_uncollectible- (if all retries fail)- Subscription may be paused or canceled depending on settings
10.5 Best Practices
-
Prioritize invoice events for billing - Monitor
invoice.paidandinvoice.payment_failedevents:invoice.paid: Confirm billing cycle payment, grant continued accessinvoice.payment_failed: Alert customer about payment issue, update payment method
-
Handle trial expiration proactively - Monitor
subscription.trial_will_end:- Send reminder emails 3 days before trial ends
- Encourage users to update payment information
- Highlight subscription benefits to reduce churn
-
Track subscription status changes - Use
subscription.updatedto:- Monitor status transitions (trialing → active, active → past_due)
- Update customer access privileges
- Trigger billing notifications
-
Manage failed payments gracefully - When
invoice.payment_failedoccurs:- Notify customers immediately
- Provide easy payment method update links
- Implement grace period before service suspension
- Track retry attempts and failure reasons
-
Monitor payment action requirements - Handle
invoice.payment_action_required:- Alert customers about required actions (e.g., 3D Secure authentication)
- Provide clear instructions and links
- Track completion rates
-
Handle paused subscriptions - Track
subscription.pausedandsubscription.resumed:- Update customer access accordingly
- Send confirmation emails
- Track pause/resume patterns for analytics
-
Reconcile billing with invoices - Use invoice events to:
- Match payments to billing cycles
- Generate accounting reports
- Track revenue by subscription
- Identify billing discrepancies
-
Handle idempotency - Store event IDs (
evt_*) to prevent duplicate processing -
Reconcile with subscription ID - Use the
subscriptionfield in invoice events to:- Match invoices to subscriptions
- Update subscription status in your system
- Track payment history per subscription
For more details on webhook setup, signature verification, and complete event schemas, see the Webhook Events documentation.
Best Practices
- Subscription Creation:
- Create subscriptions through payment intents with selling plans, not directly
- Ensure products have properly configured selling plan groups
- Set appropriate trial periods to encourage adoption
- Clearly communicate billing terms to customers
1a. Selling Plan Management:
- Selling plans cannot be deleted while active - You must first set the selling plan status to
inactivebefore deletion (Error code 274: selling_plan_active_no_delete) - When deleting a selling plan group, all associated selling plans within that group are automatically deleted
- Before deleting a selling plan, ensure no active subscriptions are using it
- Consider deactivating selling plans instead of deleting them to preserve historical data
-
Monitoring Subscriptions:
- Regularly check for
incompletesubscriptions and follow up - Monitor
past_duesubscriptions for payment failures - Track
hours_since_creationfor incomplete subscriptions - Set up alerts for subscriptions approaching expiration
- Regularly check for
-
Cancellation Handling:
- Default to
cancel_at_period_end: truefor better customer experience - Collect
cancel_reasonfor analytics and improvement insights - Consider offering pause as an alternative to cancellation
- Send confirmation emails after cancellation
- Default to
-
Pause Management:
- Use
voidbehavior for most pause scenarios - Set
resumes_atfor predictable auto-resume (e.g., seasonal business) - Document your pause policy clearly for customers
- Limit pause duration if needed (implement in your business logic)
- Use
-
Pricing Transparency:
- Display both
current_pricing_tierandupcoming_pricing_tierto customers - Show
next_charge_amountclearly before billing - Communicate when pricing will change between cycles
- Use
selling_plan_pricing.pricing_tiersto preview full pricing schedule
- Display both
-
Payment Method Management:
- Verify payment method is valid before each billing cycle
- Send reminders when payment method is expiring
- Allow customers to update payment methods easily
- Handle payment failures gracefully with retry logic
-
Customer Communication:
- Send receipts after successful payments
- Notify customers before upcoming charges
- Alert customers of payment failures
- Confirm when subscriptions are paused, resumed, or canceled
- Provide clear instructions for managing subscriptions
- Include Customer Portal links in emails for easy self-service access
- Generate fresh ephemeral tokens for each portal link to maintain security
-
Metadata Usage:
- Store subscription source (e.g., website, mobile app)
- Track marketing campaigns in metadata
- Add customer segment information
- Include any custom fields relevant to your business
-
Filtering and Search:
- Use
customer_idfilter to show customer their subscriptions - Filter by
statusfor admin dashboards - Combine filters for targeted queries
- Implement pagination for large subscription lists
- Use
-
Error Handling:
- Handle incomplete subscriptions with payment reminders
- Retry failed payments with appropriate intervals
- Provide clear error messages to customers
- Log subscription events for debugging and analytics
-
Compliance and Security:
- Store payment method information securely
- Comply with PCI DSS requirements
- Implement clear cancellation policies
- Maintain audit logs of subscription changes
- Honor customer requests promptly
-
Analytics and Reporting:
- Track subscription churn rates by analyzing cancellations
- Monitor pause vs cancel ratios
- Analyze
cancel_reasonfor improvement opportunities - Calculate lifetime value (LTV) from subscription data
- Monitor trial-to-paid conversion rates
-
Customer Portal Usage:
- Provide easy access to Customer Portal for self-service management
- Generate ephemeral tokens server-side (never in frontend)
- Include portal links in subscription confirmation emails
- Add "Manage Subscription" buttons in user dashboards
- Send portal links proactively when payment issues occur
- Use
allow_create: falsewhen generating tokens for existing customers only - Track portal usage metrics to understand customer engagement