Stripe Integration for AI SaaS Products: Lessons Learned
Why Stripe for AI SaaS
AI SaaS products have unique billing requirements that most payment systems handle poorly. You often need metered billing based on API calls or tokens used, tiered pricing that adjusts with usage, and the ability to handle variable costs that change monthly. Stripe handles all of these patterns well, which is why I use it for my commercial AI projects.
The Billing Model Challenge
Traditional SaaS uses fixed monthly subscriptions. AI SaaS often needs usage-based pricing because the cost of serving each customer varies dramatically based on their usage. A customer making 100 API calls per month costs me very different amounts from one making 10,000 calls.
I settled on a hybrid model: a base subscription plus metered usage charges. Stripe supports this natively through subscription items with metered billing:
import stripe
# Create a subscription with a base fee and metered component
subscription = stripe.Subscription.create(
customer=customer_id,
items=[
{"price": "price_base_monthly"}, # Fixed base fee
{"price": "price_per_api_call"}, # Metered usage
]
)
# Report usage throughout the billing period
def report_usage(subscription_item_id: str, quantity: int):
stripe.SubscriptionItem.create_usage_record(
subscription_item_id,
quantity=quantity,
timestamp=int(time.time()),
action='increment'
)
Tracking AI Usage for Billing
Accurately tracking AI usage is harder than it sounds. You need to count API calls, but also account for retries (which should not be billed), failed requests (billing policy varies), and the difference between input and output tokens.
I built a usage tracking middleware that sits in my FastAPI pipeline:
class UsageTracker:
def __init__(self, supabase_client, stripe_client):
self.db = supabase_client
self.stripe = stripe_client
async def record_usage(self, user_id: str, endpoint: str,
tokens_in: int, tokens_out: int,
success: bool):
# Always log to database for auditing
await self.db.table('usage_logs').insert({
'user_id': user_id,
'endpoint': endpoint,
'tokens_in': tokens_in,
'tokens_out': tokens_out,
'success': success,
'timestamp': datetime.utcnow().isoformat()
}).execute()
# Only bill for successful requests
if success:
self.stripe.report_usage(
subscription_item_id=get_subscription_item(user_id),
quantity=1
)
Webhook Handling
Stripe communicates events through webhooks. Getting webhook handling right is critical for reliable billing. Here are the events I handle:
- checkout.session.completed: New customer signed up. Provision their account
- customer.subscription.updated: Plan changed. Adjust rate limits
- customer.subscription.deleted: Subscription cancelled. Revoke access gracefully
- invoice.payment_failed: Payment failed. Send notification and enter grace period
- invoice.paid: Payment succeeded. Reset any grace period flags
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
payload = await request.body()
sig_header = request.headers.get('stripe-signature')
try:
event = stripe.Webhook.construct_event(
payload, sig_header, WEBHOOK_SECRET
)
except (ValueError, stripe.error.SignatureVerificationError):
raise HTTPException(status_code=400)
if event['type'] == 'invoice.payment_failed':
await handle_payment_failure(event['data']['object'])
elif event['type'] == 'customer.subscription.deleted':
await handle_cancellation(event['data']['object'])
return {"status": "ok"}
Lessons Learned the Hard Way
1. Always Verify Webhook Signatures
Early on, I skipped signature verification during development and forgot to add it before going live. Anyone could have sent fake webhook events to my endpoint. Always verify from day one.
2. Idempotent Webhook Handlers
Stripe may send the same webhook multiple times. Your handlers must be idempotent. I track processed event IDs in the database and skip duplicates.
3. Grace Periods for Failed Payments
Do not immediately revoke access when a payment fails. Give customers a grace period (I use 7 days) and send reminder emails. Most failed payments are due to expired cards and resolve themselves.
4. Test with Stripe CLI
The Stripe CLI lets you trigger webhook events locally during development. This is invaluable for testing payment flows without creating real transactions:
stripe listen --forward-to localhost:8000/webhooks/stripe
stripe trigger invoice.payment_failed
5. Keep Billing Logic Out of Your AI Pipeline
Billing and AI processing should be loosely coupled. Your AI pipeline should not care about billing status. A separate middleware checks subscription status before allowing requests, and a separate service handles usage reporting after requests complete.
Pricing Strategy
Getting pricing right for AI SaaS is tricky. Here is what I have learned:
- Price based on value delivered, not cost incurred. If your AI saves a customer 10 hours of work, charging based on your API costs is leaving money on the table
- Offer a free tier with strict limits. This lets potential customers evaluate your product without risk
- Provide clear usage dashboards so customers are never surprised by their bill
- Set up cost alerts that notify you when a customer's usage spikes unexpectedly
Stripe makes the payment mechanics easy. The hard part is designing a billing model that is fair to customers and sustainable for your business. Get the model right first, then implement it.
Start Simple
Do not over-engineer your billing from the start. Launch with a simple fixed-price plan and add usage-based components when you have enough data to set prices intelligently. You can always migrate customers to a new pricing model later. Stripe makes plan migrations straightforward.