Automate the full Stripe subscription lifecycle using POST /v1/subscriptions to create, PATCH /v1/subscriptions/{id} to upgrade or downgrade, and DELETE /v1/subscriptions/{id} to cancel. React to lifecycle events via webhooks: invoice.paid, invoice.payment_failed, and customer.subscription.deleted. Key gotcha: plan changes trigger proration charges by default. Rate limit: 100 req/sec in live mode; billing cycles can spike webhook volume.
API Quick Reference
API Key (Bearer token)
100 requests/second (live mode)
JSON
Available
Understanding the Stripe API
Stripe Billing is the most feature-complete subscription engine available via API. It handles recurring invoicing, automatic payment retry (dunning), proration for mid-cycle plan changes, trial periods, usage-based billing, and subscription pausing — all configurable via REST API calls.
The subscription lifecycle flows through several Stripe objects: Customer → PaymentMethod → Subscription → Invoice → Charge. When a subscription renews, Stripe automatically creates an Invoice, attempts to charge the saved PaymentMethod, and fires webhook events for each state change. Your application reacts to those webhooks to update user entitlements, send confirmation emails, and trigger dunning sequences for failed payments.
The critical integration pattern for SaaS: always use webhooks rather than polling to update subscription status. Never rely on the API response from the create/update call alone — the payment may still fail asynchronously. Official documentation: https://stripe.com/docs/api/subscriptions
https://api.stripe.comSetting Up Stripe API Authentication
Stripe Billing uses two keys for different purposes: the secret key (sk_*) for all server-side subscription management, and the publishable key (pk_*) for client-side Checkout Sessions and Elements. The Stripe SDK accepts the secret key directly at initialization. Test mode subscriptions use sk_test_* and generate no real charges.
- 1Log in to your Stripe Dashboard at dashboard.stripe.com
- 2Click 'Developers' then 'API keys' in the top navigation
- 3Copy your secret key (sk_live_* for production) and publishable key (pk_live_* for frontend)
- 4Set environment variables: STRIPE_SECRET_KEY=sk_live_... and STRIPE_PUBLISHABLE_KEY=pk_live_...
- 5Set up a webhook endpoint at Developers > Webhooks > Add endpoint, subscribing to: invoice.paid, invoice.payment_failed, customer.subscription.deleted, customer.subscription.updated
- 6Copy the webhook signing secret (whsec_*) and set STRIPE_WEBHOOK_SECRET environment variable
- 7In your Stripe Dashboard, create Products and Prices (the price ID like price_* is what you pass to the subscriptions API)
1import os2import stripe34stripe.api_key = os.environ.get('STRIPE_SECRET_KEY')56# Verify connectivity and list available prices7prices = stripe.Price.list(active=True, limit=5)8for price in prices.data:9 print(f'{price.id}: {price.currency.upper()} {price.unit_amount/100:.2f}/{price.recurring.interval}')Security notes
- •Store sk_live_* in server environment variables — never in frontend code, mobile apps, or git repositories
- •The publishable key pk_live_* is safe for frontend use — it cannot make server-side charges alone
- •Store the webhook signing secret (whsec_*) separately from the API key
- •Use test mode (sk_test_* + pk_test_*) for development — test subscriptions never charge real cards
- •Verify webhook signatures on every incoming event before processing to prevent replay attacks
- •Rotate API keys at Developers > API keys if you suspect exposure
Key endpoints
/v1/subscriptionsCreates a new subscription for an existing Stripe customer. Requires a customer ID and a price ID. The subscription immediately attempts to collect payment unless you set payment_behavior or use a trial period.
| Parameter | Type | Required | Description |
|---|---|---|---|
customer | string | required | The Stripe customer ID (cus_*) for the subscriber |
items | array | required | Array of subscription items, each with a price ID. The price defines the amount and billing interval. |
trial_end | number | optional | Unix timestamp for when the trial ends and billing begins. Use 'now' to end an active trial. |
payment_behavior | string | optional | allow_incomplete: subscription active even if payment fails. default_incomplete: subscription incomplete until payment succeeds. |
proration_behavior | string | optional | For plan changes: always_invoice (charge proration immediately), create_prorations (prorate on next invoice), or none. |
Request
1{"customer": "cus_PqKLt2eZvKYlo2C", "items": [{"price": "price_1OqKLt2eZvKYlo2Cghi"}], "payment_settings": {"save_default_payment_method": "on_subscription"}, "expand": ["latest_invoice.payment_intent"]}Response
1{"id": "sub_1OqKMj2eZvKYlo2C", "object": "subscription", "customer": "cus_PqKLt2eZvKYlo2C", "status": "active", "current_period_start": 1706745820, "current_period_end": 1709424220, "items": {"data": [{"id": "si_1OqKMj", "price": {"id": "price_1OqKLt", "unit_amount": 2900, "currency": "usd", "recurring": {"interval": "month"}}}]}, "latest_invoice": {"payment_intent": {"status": "succeeded"}}}/v1/subscriptions/{id}Updates an existing subscription — change the plan, add items, modify trial, pause, or update payment settings. Used for upgrades, downgrades, and mid-cycle changes.
| Parameter | Type | Required | Description |
|---|---|---|---|
items | array | optional | New subscription items. For plan changes, include the subscription item ID (si_*) you're replacing. |
proration_behavior | string | optional | Controls how proration is charged on plan changes. always_invoice charges immediately; none skips proration. |
cancel_at_period_end | boolean | optional | If true, subscription cancels at the end of the current billing period instead of immediately. |
Request
1{"items": [{"id": "si_1OqKMj", "price": "price_1NewPlanId"}], "proration_behavior": "always_invoice"}Response
1{"id": "sub_1OqKMj2eZvKYlo2C", "status": "active", "items": {"data": [{"id": "si_1OqKMj", "price": {"id": "price_1NewPlanId", "unit_amount": 9900}}]}}/v1/subscriptions/{id}Cancels a subscription. Immediate cancellation stops billing now; set cancel_at_period_end=true on the update endpoint instead for end-of-period cancellation.
| Parameter | Type | Required | Description |
|---|---|---|---|
cancellation_details.feedback | string | optional | Reason for cancellation: customer_service, low_quality, missing_features, other, switched_service, too_complex, too_expensive, unused |
Request
1{"cancellation_details": {"comment": "Canceled via self-service portal", "feedback": "too_expensive"}}Response
1{"id": "sub_1OqKMj2eZvKYlo2C", "status": "canceled", "canceled_at": 1706745900, "current_period_end": 1709424220}/v1/subscriptionsLists subscriptions with optional filters. Use status parameter to filter active, trialing, past_due, canceled, or unpaid subscriptions. Use customer to get all subscriptions for a specific customer.
| Parameter | Type | Required | Description |
|---|---|---|---|
customer | string | optional | Filter by customer ID to get all their subscriptions |
status | string | optional | Filter by status: active, trialing, past_due, canceled, incomplete, incomplete_expired, unpaid |
price | string | optional | Filter subscriptions on a specific price ID |
Response
1{"object": "list", "data": [{"id": "sub_1OqKMj", "status": "active", "customer": "cus_PqKLt2", "current_period_end": 1709424220}], "has_more": false}Step-by-step automation
Create a Customer and Attach a Payment Method
Why: Subscriptions require an existing Stripe Customer with a saved payment method — creating the customer first keeps your billing data clean and linked to your internal user ID.
Create a Stripe Customer with your internal user ID in metadata, then attach a PaymentMethod and set it as default. In practice, you collect the payment method via Stripe Elements or Checkout on the frontend — the PaymentMethod ID is returned to your server after the customer enters their card. Never handle raw card numbers server-side.
1# 1. Create customer2curl https://api.stripe.com/v1/customers \3 -u "$STRIPE_SECRET_KEY:" \4 -d email="user@example.com" \5 -d "metadata[user_id]"="usr_123abc"67# 2. Attach payment method (pm_* from Stripe.js/Elements on frontend)8curl https://api.stripe.com/v1/payment_methods/pm_1OqKLt2eZvKYlo2Cabc/attach \9 -u "$STRIPE_SECRET_KEY:" \10 -d customer="cus_PqKLt2eZvKYlo2C"1112# 3. Set as default13curl https://api.stripe.com/v1/customers/cus_PqKLt2eZvKYlo2C \14 -u "$STRIPE_SECRET_KEY:" \15 -d "invoice_settings[default_payment_method]"="pm_1OqKLt2eZvKYlo2Cabc"Pro tip: Store the Stripe customer ID on your user record immediately after creation. A user should have exactly one Stripe customer — check for existing customer IDs before creating new ones to avoid duplicates.
Expected result: A Stripe Customer ID (cus_*) with a default payment method attached, ready for subscription creation.
Create the Subscription
Why: Creating the subscription triggers the first invoice and payment attempt — the expand parameter on latest_invoice.payment_intent lets you confirm payment succeeded in the same call.
Call POST /v1/subscriptions with the customer ID and price ID from your Stripe Dashboard. Expand latest_invoice.payment_intent to see payment status in the response. If status is 'active', billing succeeded. If status is 'incomplete', the payment requires additional action (3D Secure). Use payment_behavior='default_incomplete' if you want to confirm payment before activating.
1curl https://api.stripe.com/v1/subscriptions \2 -u "$STRIPE_SECRET_KEY:" \3 -d customer="cus_PqKLt2eZvKYlo2C" \4 -d "items[0][price]"="price_1OqKLt2eZvKYlo2Cghi" \5 -d "payment_settings[save_default_payment_method]"="on_subscription" \6 -d "expand[]"="latest_invoice.payment_intent"Pro tip: Use payment_behavior='default_incomplete' when creating subscriptions — the subscription stays inactive until payment succeeds, preventing you from granting access before you've confirmed billing.
Expected result: A subscription object with status 'active' (payment succeeded) or 'trialing' (trial started). If status is 'incomplete', return the client_secret to the frontend for 3D Secure confirmation.
Handle Plan Upgrades and Downgrades
Why: Proration is applied by default when changing plans mid-cycle — understanding this prevents surprise charges to your customers.
To change a subscription's plan, PATCH /v1/subscriptions/{id} with the new price and the subscription item ID (si_*) you're replacing. Set proration_behavior: 'always_invoice' to charge the prorated difference immediately, 'create_prorations' to include it on the next invoice, or 'none' to skip proration entirely. For upgrades, always_invoice is the clearest customer experience.
1# Get the subscription item ID first2SUB_ITEM_ID=$(curl -s https://api.stripe.com/v1/subscriptions/sub_1OqKMj2eZvKYlo2C \3 -u "$STRIPE_SECRET_KEY:" | python3 -c "import sys,json; print(json.load(sys.stdin)['items']['data'][0]['id'])")45# Upgrade to new price, charge proration immediately6curl -X POST https://api.stripe.com/v1/subscriptions/sub_1OqKMj2eZvKYlo2C \7 -u "$STRIPE_SECRET_KEY:" \8 -d "items[0][id]"="$SUB_ITEM_ID" \9 -d "items[0][price]"="price_1NewPlanId" \10 -d proration_behavior="always_invoice"Pro tip: Preview proration before charging by calling POST /v1/subscriptions/{id}/upcoming_invoice (not a real invoice — just a preview). Show customers the prorated amount before they confirm the upgrade.
Expected result: The subscription updated with the new price. If proration='always_invoice', a new invoice is created and immediately charged for the prorated difference.
Handle Dunning with invoice.payment_failed Webhook
Why: Stripe retries failed payments automatically, but your application needs to know when a payment fails to restrict access, send customer notifications, and track dunning state.
Listen for invoice.payment_failed webhook events. When received, retrieve the subscription to check its status — 'past_due' means Stripe is still retrying, 'unpaid' means all retries exhausted. Use Stripe's Smart Retries (enabled by default in Billing settings) which retries 4 times over ~3 weeks. Add your own email/SMS notification on each failure event.
1# Register webhook for payment failure events2curl https://api.stripe.com/v1/webhook_endpoints \3 -u "$STRIPE_SECRET_KEY:" \4 -d url="https://yourapp.com/webhooks/stripe" \5 -d "enabled_events[]"="invoice.payment_failed" \6 -d "enabled_events[]"="invoice.paid" \7 -d "enabled_events[]"="customer.subscription.deleted"Pro tip: Stripe's Smart Retries intelligently schedules retry attempts based on machine learning signals about when the card is likely to succeed. Enable it in your Stripe Dashboard under Billing > Settings > Smart Retries.
Expected result: Your webhook handler receives and processes subscription lifecycle events, granting or revoking access based on payment status.
Complete working code
A complete subscription management module handling creation, plan changes, cancellation, and webhook processing. This shows the full integration pattern for a SaaS application — create customers, manage subscriptions, and react to billing events.
1import stripe2import os3import logging4from flask import Flask, request, jsonify56logging.basicConfig(level=logging.INFO)7log = logging.getLogger(__name__)89stripe.api_key = os.environ['STRIPE_SECRET_KEY']10WEBHOOK_SECRET = os.environ['STRIPE_WEBHOOK_SECRET']1112app = Flask(__name__)1314# --- Subscription Management Functions ---1516def get_or_create_customer(email, user_id):17 existing = stripe.Customer.search(query=f'metadata["user_id"]:"{user_id}"')18 if existing.data:19 return existing.data[0].id20 customer = stripe.Customer.create(email=email, metadata={'user_id': user_id})21 log.info(f'Created customer {customer.id} for user {user_id}')22 return customer.id2324def create_subscription(customer_id, price_id, trial_days=0):25 params = {26 'customer': customer_id,27 'items': [{'price': price_id}],28 'payment_settings': {'save_default_payment_method': 'on_subscription'},29 'expand': ['latest_invoice.payment_intent']30 }31 if trial_days > 0:32 import time33 params['trial_end'] = int(time.time()) + trial_days * 864003435 sub = stripe.Subscription.create(**params)36 log.info(f'Subscription {sub.id} created, status: {sub.status}')37 return sub3839def cancel_subscription(subscription_id, at_period_end=True):40 if at_period_end:41 sub = stripe.Subscription.modify(subscription_id, cancel_at_period_end=True)42 log.info(f'Subscription {subscription_id} will cancel at period end')43 else:44 sub = stripe.Subscription.delete(subscription_id)45 log.info(f'Subscription {subscription_id} canceled immediately')46 return sub4748# --- Webhook Handler ---4950@app.route('/webhooks/stripe', methods=['POST'])51def webhook():52 payload = request.get_data() # RAW body — must not be parsed first53 sig = request.headers.get('Stripe-Signature')5455 try:56 event = stripe.Webhook.construct_event(payload, sig, WEBHOOK_SECRET)57 except stripe.error.SignatureVerificationError:58 log.warning('Invalid webhook signature')59 return jsonify({'error': 'Invalid signature'}), 4006061 obj = event.data.object62 etype = event.type6364 if etype == 'invoice.paid':65 log.info(f'Invoice paid: sub={obj.subscription}, customer={obj.customer}')66 # grant_user_access(obj.customer)6768 elif etype == 'invoice.payment_failed':69 log.warning(f'Payment failed attempt {obj.attempt_count}: sub={obj.subscription}')70 # send_dunning_email(obj.customer_email, obj.attempt_count)71 # if obj.attempt_count >= 3: restrict_access(obj.customer)7273 elif etype == 'customer.subscription.updated':74 log.info(f'Subscription updated: {obj.id}, status={obj.status}')7576 elif etype == 'customer.subscription.deleted':77 log.info(f'Subscription canceled: {obj.id}')78 # revoke_user_access(obj.customer)7980 return jsonify({'status': 'ok'})8182if __name__ == '__main__':83 app.run(port=3000)Error handling
This customer has no attached payment source or default payment method.Attempting to create a subscription without a saved payment method on the customer. Stripe can't bill a customer without a payment method.
Attach a PaymentMethod to the customer and set it as default_payment_method in customer.invoice_settings before creating the subscription. Use Stripe Checkout or Elements to collect card details.
No retry — add a payment method first
No such price: 'price_...'The price ID doesn't exist in the Stripe account, is in the wrong environment (test vs live), or was deleted.
Verify the price ID in your Stripe Dashboard under Products. Confirm you're using sk_live_* with live-mode price IDs and sk_test_* with test-mode price IDs.
No retry — fix the price ID
Your card was declined.The customer's card was declined during subscription creation or renewal. Common reasons: insufficient funds, expired card, fraud detection.
Return the payment intent client_secret to the frontend and use stripe.confirmCardPayment() to let the customer retry or update their card. For renewals, the invoice.payment_failed webhook fires and Stripe retries automatically.
For initial creation: collect new payment method. For renewals: Stripe's Smart Retries handles this automatically.
Webhook signature verification failedWebhook body was parsed (e.g., via body-parser JSON middleware) before being passed to stripe.webhooks.constructEvent(). Parsing modifies the body and breaks the HMAC verification.
In Express, use express.raw({type: 'application/json'}) for the webhook route. In Flask, use request.get_data() not request.json. The raw bytes must be passed to constructEvent unchanged.
No retry — fix the middleware configuration
Too many requestsExceeded 100 req/sec rate limit. Can occur when processing many subscription changes simultaneously or handling a billing cycle spike with thousands of webhook events.
Process subscription changes with a queue (Redis, SQS) rather than all at once. For webhook handling, return 200 immediately and process asynchronously to avoid timeout-based retries that compound the load.
Exponential backoff: 1s, 2s, 4s, 8s. Honor Retry-After header if present.
Rate Limits for Stripe API
| Scope | Limit | Window |
|---|---|---|
| Live mode | 100 requests | per second |
| Test mode | 25 requests | per second |
| Billing cycle webhook burst | 1 event per subscription | per renewal — can be 1,000+ simultaneously |
1import time2import stripe34def stripe_with_retry(fn, max_retries=5):5 for attempt in range(max_retries):6 try:7 return fn()8 except stripe.error.RateLimitError:9 wait = min(2 ** attempt, 32)10 print(f'Rate limit hit, waiting {wait}s')11 time.sleep(wait)12 except stripe.error.APIConnectionError as e:13 if attempt == max_retries - 1: raise14 time.sleep(2 ** attempt)15 raise Exception('Max retries exceeded')- Process subscription changes asynchronously via a job queue (Celery, BullMQ) to avoid blocking requests and to handle billing cycle webhook spikes
- Return 200 from webhook endpoints immediately, then process the event asynchronously — Stripe marks your endpoint as failed if no 2xx within 30 seconds
- Use Stripe's idempotency keys when creating subscriptions: add Idempotency-Key: {unique-id} header to prevent duplicate subscriptions from retry logic
- Cache subscription status in your database and update via webhooks — avoid calling GET /v1/subscriptions on every page load
- Subscribe only to the webhook events you handle — unused events still count toward webhook delivery retries
Security checklist
- Store STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in environment variables — never in source code
- Verify webhook signatures with stripe.webhooks.constructEvent() using raw request body on every webhook request
- Use payment_behavior='default_incomplete' on subscription creation to prevent access grants before payment confirmation
- Validate subscription status from webhook events, not from the initial API response — payments can fail asynchronously
- Implement idempotency keys on subscription creation to prevent duplicate billing from network retries
- Never grant access based solely on the subscription creation API response — always wait for invoice.paid webhook confirmation
- Use Restricted API keys with only Subscriptions and Customers write access for subscription management automation
- Log all subscription state changes with timestamps for audit trails and customer support
Automation use cases
SaaS Onboarding Flow
intermediateCollect payment via Stripe Elements, create customer and subscription, then grant application access only after receiving the invoice.paid webhook confirmation.
Dunning Email Sequence
intermediateSend progressive dunning emails on invoice.payment_failed events — day 1: gentle reminder, day 7: card update link, day 14: account suspension warning.
Usage-Based Billing
advancedReport usage quantities via stripe.SubscriptionItem.create_usage_record() for metered pricing, then let Stripe calculate and invoice automatically at period end.
Annual Plan Upgrade Incentive
intermediateDetect monthly subscribers approaching 12 months and offer an annual plan switch with proration preview, then process the change via the API if accepted.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Free tier (100 tasks/month), paid from $19.99/monthZapier's Stripe integration can create subscriptions and react to lifecycle events, but complex dunning sequences and plan change logic require multiple multi-step Zaps.
- + No code required
- + Connects Stripe to CRM and email tools easily
- + Quick setup for simple flows
- - Limited control over proration behavior
- - No support for complex dunning logic
- - Costs per task at scale
Make
Free tier (1,000 ops/month), paid from $9/monthMake's Stripe module handles subscription creation and webhook responses with more control than Zapier, including conditional branching for dunning sequences.
- + Visual dunning workflow builder
- + Better conditional logic than Zapier
- + Webhook trigger support
- - Subscription management still limited vs native API
- - Monthly operation limits
- - No proration preview support
n8n
Free (self-hosted), Cloud from €20/monthn8n's HTTP Request node can call all Stripe subscription endpoints, with webhook trigger nodes for lifecycle events — gives full API access with a visual builder.
- + Full Stripe API access including proration behavior
- + Self-hostable
- + Webhook trigger nodes built-in
- - Requires technical configuration
- - No native Stripe module
- - Infrastructure management on self-hosted
Best practices
- Grant application access based on invoice.paid webhook events, not the subscription creation API response — payment can fail asynchronously
- Use payment_behavior='default_incomplete' for subscription creation so the subscription stays inactive until first payment succeeds
- Preview proration amounts with POST /v1/subscriptions/{id}/upcoming_invoice before applying plan changes to show customers what they'll be charged
- Always specify cancel_at_period_end=true for customer-initiated cancellations — immediate cancellation with no refund is surprising and generates disputes
- Implement idempotency keys (Idempotency-Key header) for subscription creation to prevent duplicate charges from network timeouts
- Use Stripe's Smart Retries (Billing > Settings) which automatically retries failed payments at optimal times based on card network signals
- Store Stripe customer IDs and subscription IDs in your database immediately on creation — use Stripe's customer search as a fallback only
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I'm building Stripe subscription management in Python with Flask. I use stripe.Subscription.create() with expand=['latest_invoice.payment_intent'] and handle webhooks with stripe.Webhook.construct_event(). Help me: (1) implement the full webhook handler for invoice.paid, invoice.payment_failed, and customer.subscription.deleted events, (2) add idempotency keys to subscription creation, (3) preview proration before plan changes using the upcoming invoice endpoint, and (4) handle 3D Secure (requires_action payment intent status) by returning the client_secret to my frontend.
Build a Stripe subscription management UI in React with these features: (1) a 'Subscribe' button that creates a Stripe Checkout session via a backend API endpoint, (2) a subscription status card showing current plan, next billing date, and amount, (3) a 'Change Plan' modal with plan options and a proration preview fetched from the backend, (4) a 'Cancel Subscription' button with confirmation dialog and cancel_at_period_end option, (5) a billing history table showing past invoices with download links. The backend endpoints are: POST /api/subscribe, GET /api/subscription, PATCH /api/subscription/change-plan, DELETE /api/subscription.
Frequently asked questions
What's the difference between cancel_at_period_end and DELETE /v1/subscriptions?
Setting cancel_at_period_end=true via PATCH keeps the subscription active until the end of the current billing period, then cancels — the customer retains access and you don't owe a refund. DELETE /v1/subscriptions/{id} cancels immediately with no prorated refund by default. For customer-initiated cancellations, always use cancel_at_period_end=true. For fraud or ToS violations, use immediate cancellation.
What happens with proration when a customer upgrades mid-cycle?
By default (proration_behavior='create_prorations'), Stripe calculates the unused portion of the current plan as a credit and the remaining days on the new plan as a charge. These appear as proration line items on the next invoice. Use proration_behavior='always_invoice' to charge the difference immediately (better UX), or 'none' to skip proration entirely (simpler but customer pays full price on both plans for the overlap period).
How many times does Stripe retry a failed subscription payment?
Stripe Billing's Smart Retries attempts payment up to 4 times over approximately 3 weeks by default. The exact timing is determined by machine learning based on when the card is most likely to succeed. You can customize the retry schedule in Stripe Dashboard > Billing > Settings > Smart Retries. After all retries fail, the subscription moves to 'unpaid' status and the customer.subscription.deleted event fires if you've configured it to cancel on unpaid.
Why does my webhook keep failing signature verification?
The most common cause is parsing the request body before passing it to stripe.webhooks.constructEvent(). In Express.js, using express.json() middleware on the webhook route parses the body, which changes it and breaks the HMAC signature. Use express.raw({type: 'application/json'}) specifically for the webhook route. In Flask, use request.get_data() not request.json. The raw bytes from the request must be passed to constructEvent unchanged.
What happens when I hit the rate limit during a billing cycle?
Stripe returns a 429 Too Many Requests error. This can happen if you're processing thousands of subscription updates simultaneously. The fix: use a job queue (Celery, BullMQ) to process subscription changes asynchronously. For webhooks, return 200 immediately and process asynchronously — if your endpoint times out (30 seconds), Stripe retries the webhook, which compounds the load.
Is Stripe Billing free to use?
Stripe Billing has a fee of 0.5% of recurring revenue for standard plans or 0.8% for custom billing features (revenue recognition, prorations, multi-currency). This is on top of Stripe's standard payment processing fee (2.9% + $0.30 per transaction). There's no monthly subscription fee — you only pay a percentage of what you bill through Stripe.
Can RapidDev help build a complete subscription billing system?
Yes. RapidDev has built 600+ apps including full SaaS billing systems with subscription management, dunning sequences, usage-based billing, and revenue dashboards. If you need a custom Stripe Billing integration, get a free consultation at rapidevelopers.com.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation