Build Paystack Subscriptions in FastAPI
To build Paystack subscriptions in FastAPI, create a plan via the API, initialize a transaction with the plan code using an async endpoint, handle subscription webhooks to track status, and use FastAPI dependencies to gate premium endpoints behind active subscription checks.
Subscribe Endpoint
@app.post('/api/subscribe')
async def subscribe(plan_code: str, current_user = Depends(get_current_user)):
async with httpx.AsyncClient() as client:
response = await client.post(
'https://api.paystack.co/transaction/initialize',
json={
'email': current_user.email,
'plan': plan_code,
'callback_url': BASE_URL + '/subscription/callback',
},
headers={
'Authorization': 'Bearer ' + PAYSTACK_SECRET,
'Content-Type': 'application/json',
},
)
data = response.json()
if not data.get('status'):
raise HTTPException(status_code=400, detail=data.get('message'))
return {
'authorization_url': data['data']['authorization_url'],
'reference': data['data']['reference'],
}
Subscription Webhook Handlers
async def handle_subscription_create(data: dict):
email = data['customer']['email']
user = await db.get_user_by_email(email)
if not user:
return
await db.upsert_subscription({
'user_id': user.id,
'subscription_code': data['subscription_code'],
'plan_code': data['plan']['plan_code'],
'email_token': data.get('email_token', ''),
'status': 'active',
'next_payment_date': data.get('next_payment_date'),
})
async def handle_invoice_failed(data: dict):
sub_code = data.get('subscription', {}).get('subscription_code')
if sub_code:
await db.update_subscription_status(sub_code, 'past_due')
Subscription Access Dependency
# dependencies.py
async def require_active_subscription(current_user = Depends(get_current_user)):
subscription = await db.get_subscription(current_user.id)
if not subscription or subscription.status != 'active':
raise HTTPException(
status_code=403,
detail='An active subscription is required',
)
return subscription
# Usage:
@app.get('/api/premium')
async def premium_content(sub = Depends(require_active_subscription)):
return {'content': 'Premium data', 'plan': sub.plan_code}
Cancel Subscription
@app.post('/api/subscription/cancel')
async def cancel_subscription(current_user = Depends(get_current_user)):
subscription = await db.get_subscription(current_user.id)
if not subscription:
raise HTTPException(status_code=404, detail='No subscription found')
async with httpx.AsyncClient() as client:
response = await client.post(
'https://api.paystack.co/subscription/disable',
json={
'code': subscription.subscription_code,
'token': subscription.email_token,
},
headers={
'Authorization': 'Bearer ' + PAYSTACK_SECRET,
'Content-Type': 'application/json',
},
)
data = response.json()
if data.get('status'):
await db.update_subscription_status(subscription.subscription_code, 'cancelled')
return {'message': 'Subscription cancelled'}
raise HTTPException(status_code=400, detail=data.get('message'))
Subscription Status Endpoint
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class SubscriptionStatus(BaseModel):
has_subscription: bool
status: Optional[str] = None
plan_code: Optional[str] = None
next_payment_date: Optional[datetime] = None
@app.get('/api/subscription/status', response_model=SubscriptionStatus)
async def subscription_status(current_user = Depends(get_current_user)):
subscription = await db.get_subscription(current_user.id)
if not subscription:
return SubscriptionStatus(has_subscription=False)
return SubscriptionStatus(
has_subscription=True,
status=subscription.status,
plan_code=subscription.plan_code,
next_payment_date=subscription.next_payment_date,
)
Key Takeaways
- ✓Paystack handles all recurring billing. You create plans, subscribe customers, and Paystack charges them automatically.
- ✓Use async httpx calls for all Paystack API interactions in FastAPI.
- ✓Webhook events drive subscription state. Handle subscription.create, charge.success, and invoice.payment_failed.
- ✓FastAPI dependencies work well for subscription access control. Create a dependency that checks the user has an active subscription.
- ✓Store subscription_code and email_token from the webhook. You need both for managing subscriptions.
- ✓Pydantic models give you type-safe subscription responses for your frontend.
Frequently Asked Questions
- Can I offer free trials with Paystack and FastAPI?
- Paystack does not have native free trials. Implement trials in your FastAPI app: grant access for the trial period, then initialize the subscription payment when the trial ends.
- How do I handle plan upgrades?
- Cancel the current subscription and create a new one on the desired plan. Handle prorating in your application logic.
- What is the email_token?
- The email_token is sent in the subscription.create webhook. You need it alongside the subscription_code to cancel a subscription via the Paystack API. Store it when you receive the webhook.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon