React + Python Setup

Set up Lumen with React frontend and Python backend (Flask, FastAPI, or Django): connect Stripe, install packages, and implement billing.

1. Connect Stripe & Create Lumen Account

  1. Stripe Keys: Get your test mode Secret and Publishable keys from Stripe Dashboard.

    Using Dodo Payments?

    Dodo Payments works similarly to Stripe but uses a single API Key (Secret Key) instead of two.

    Just like Stripe, Dodo has Test Mode and Live Mode keys. Make sure to use your Test Key for this guide.

  2. Lumen Account: Sign up at getlumen.dev.

Tip

We recommend creating two separate Lumen accounts (e.g. yourname+test@gmail.com and yourname@gmail.com) to keep test and live data completely separate. Alternatively, you can swap your test keys for live keys later.

  1. Onboarding: Enter your Stripe test keys and select USD as the currency.

2. Install Packages

Backend (Python):

# Install Flask with async support
pip install flask[async]

# Install Lumen SDK with Flask support
pip install lumen-python-sdk[flask]

Important

The Lumen SDK uses async functions, so you need flask[async] which includes async support for Flask 3.0+.

# Install Lumen SDK with FastAPI support
pip install lumen-python-sdk[fastapi]

FastAPI is async-native. The Lumen SDK will work seamlessly with async routes.

# Install Lumen SDK with Django support
pip install lumen-python-sdk[django]

The Lumen SDK works with Django 3.1+ (which has built-in async support).

Requirements: Python >= 3.8

Frontend (React):

First, install the React SDK:

npm install @getlumen/react

Then, install the UI components. Choose the method that works for your setup:

Recommended method - Uses shadcn CLI to install components

npx shadcn@latest add https://getlumen.dev/pricing-table.json
npx shadcn@latest add https://getlumen.dev/usage-badge.json

This method works even if you don't have shadcn/ui set up in your project. The shadcn CLI will:

  • Download the PricingTable and UsageBadge components
  • Automatically configure your components.json if it doesn't exist
  • Set up the necessary file structure

Requirements

Requires React and Tailwind CSS installed in your project.

Alternative method - Direct component installation

npx lumen add

This interactive command will:

  • Download the PricingTable component (TypeScript or JavaScript)
  • Configure Tailwind CSS automatically
  • Add required CSS variables

For non-interactive installation:

# TypeScript
npx lumen add -l tsx -p src/components/pricing-table.tsx -y

# JavaScript
npx lumen add -l jsx -p src/components/pricing-table.jsx -y

Requirements

This method requires Tailwind CSS installed in your project.

3. Environment Variables

During onboarding, you should have gotten Lumen API keys.

Backend (.env):

# you can get new keys at https://getlumen.dev/developer/apikeys if you lost them
LUMEN_API_KEY=lumen_sk_...
LUMEN_API_URL=https://api.getlumen.dev  # Optional, defaults to production

Frontend (.env):

VITE_LUMEN_PUBLISHABLE_KEY=pk_live_...

4. Lumen Handler (Backend)

Create a handler route to securely proxy Lumen requests (Frontend → Backend → Lumen).

Important

Make sure to update the code to use your auth middleware and get the user id from the request.

from flask import Flask, request
from lumen.handlers import lumen_flask_handler

app = Flask(__name__)

# Your auth function
def get_user_id():
    # Example: Extract from JWT or session
    user_id = request.headers.get("X-User-ID")
    if not user_id:
        return None
    return user_id

# Lumen proxy route
@app.route("/api/lumen/<path:path>", methods=["GET", "POST", "PUT", "DELETE"])
def lumen_proxy(path):
    handler = lumen_flask_handler(get_user_id=get_user_id)
    return handler(path)

if __name__ == "__main__":
    app.run(port=5000)
from fastapi import FastAPI, Request, HTTPException
from lumen.handlers import lumen_fastapi_handler

app = FastAPI()

# Your auth function
def get_user_id(request: Request):
    # Example: Extract from JWT or session
    user_id = getattr(request.state, "user_id", None)
    if not user_id:
        raise HTTPException(status_code=401, detail="Unauthorized")
    return user_id

# Create handler
handler = lumen_fastapi_handler(get_user_id=get_user_id)

# Lumen proxy route
@app.api_route("/api/lumen/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def lumen_proxy(request: Request, path: str):
    return await handler(request, path)
# views.py
from lumen.handlers import lumen_django_handler

def get_user_id(request):
    if not request.user.is_authenticated:
        return None
    return str(request.user.id)

lumen_view = lumen_django_handler(get_user_id=get_user_id)

# urls.py
from django.urls import path
from .views import lumen_view

urlpatterns = [
    path('api/lumen/<path:path>', lumen_view),
]

5. Setup LumenProvider (Frontend)

To use Lumen's hooks and components, wrap your React app with the LumenProvider.

// src/main.tsx or src/App.tsx
import { LumenProvider } from "@getlumen/react";

function App() {
  return (
    <LumenProvider config={{ apiProxyUrl: "http://localhost:5000/api/lumen" }}>
      {/* Your app components */}
    </LumenProvider>
  );
}

export default App;

Production

In production, replace http://localhost:5000 with your actual backend URL. FastAPI typically runs on port 8000.

Lumen works best if every user has a subscription, even if it's just a free plan. This ensures that:

  1. Entitlements work immediately: You can check feature access for all users.
  2. Smooth Upgrades: Users can upgrade from Free → Pro without data migration issues.

To achieve this, we listen to user.created events from your auth provider to automatically create a free customer in Lumen.

  1. Go to Configure > Webhooks in Clerk and create a webhook for user.created.

  2. URL: https://api.getlumen.dev/v1/webhooks/clerk

    Clerk Webhook

  3. In the Advanced tab, add this header:

    • Key: Authorization
    • Value: Bearer your_lumen_secret_key (from API Keys)

    Clerk webhook header

  1. Go to Webhooks > Create a new hook.

  2. Trigger on INSERT in auth.users.

  3. URL: https://api.getlumen.dev/v1/webhooks/supabase

  4. Add this HTTP Header:

    • Authorization: Bearer your_lumen_secret_key

    Supabase webhook

Install and configure the Lumen plugin in your frontend project:

npm install @getlumen/better-auth
import { LumenPlugin } from "@getlumen/better-auth";

export const auth = betterAuth({
  plugins: [ LumenPlugin({ apiKey: process.env.LUMEN_API_KEY! }) ],
});

The plugin automatically enrolls new users in Lumen when they sign up.

For any other auth system, call enroll_user in your signup endpoint after creating the user in your database:

from lumen import enroll_user
from flask import request

@app.route('/api/auth/signup', methods=['POST'])
async def signup():
    data = request.get_json()
    email = data['email']
    name = data['name']
    password = data['password']

    # 1. Create user in your auth system/database
    user = create_user(email=email, name=name, password=password)

    # 2. Enroll user in Lumen (creates free customer with subscription)
    await enroll_user(
        email=user.email,
        name=user.name,
        user_id=user.id,  # Must match the ID from your auth system
    )

    return {'success': True, 'userId': user.id}
from lumen import enroll_user
from pydantic import BaseModel

class SignupRequest(BaseModel):
    email: str
    name: str
    password: str

@app.post('/api/auth/signup')
async def signup(request: SignupRequest):
    # 1. Create user in your auth system/database
    user = await create_user(
        email=request.email,
        name=request.name,
        password=request.password
    )

    # 2. Enroll user in Lumen (creates free customer with subscription)
    await enroll_user(
        email=user.email,
        name=user.name,
        user_id=user.id,  # Must match the ID from your auth system
    )

    return {'success': True, 'userId': user.id}
from lumen import enroll_user
import json
from django.http import JsonResponse

async def signup_view(request):
    data = json.loads(request.body)
    email = data['email']
    name = data['name']
    password = data['password']

    # 1. Create user in your auth system/database
    user = create_user(email=email, name=name, password=password)

    # 2. Enroll user in Lumen (creates free customer with subscription)
    await enroll_user(
        email=user.email,
        name=user.name,
        user_id=user.id,  # Must match the ID from your auth system
    )

    return JsonResponse({'success': True, 'userId': user.id})

Important: The user_id passed to Lumen must match the user ID from your authentication system, as this is how Lumen links subscriptions to users.

7. Create a Plan

In the Lumen Dashboard, go to Plans → Create. Let's create a test plan:

  1. Basic Info: Name it "Premium Plan".
  2. Pricing: Set Monthly Price to $10. Toggle on yearly price and set it to $100.
  3. Features: Click Add New Feature to add entitlements:
    • Boolean Feature: Name it "Access to live customer service".
    • Usage Feature: Name it "API Calls". Enable credit allowance of 500 (Monthly renewal) and set overage price to $0.05.
See screenshots of plan configuration

Quickstart Plan Price

Quickstart Features

8. Add Pricing Table (Frontend)

Create a pricing page and drop in the PricingTable component. This component handles the full checkout flow automatically.

Import path depends on your installation method:

  • Lumen CLI: Import from the path where you installed it (e.g., ./components/pricing-table)
  • shadcn: Import from @/components/ui/pricing-table
// src/pages/Pricing.tsx
import { PricingTable } from "./components/pricing-table"; // or "@/components/ui/pricing-table" if using shadcn
import { useAuth } from "./your-auth-provider"; // Your auth hook

export default function PricingPage() {
  const { userId } = useAuth();

  return (
    <PricingTable
      lumenPublishableKey={import.meta.env.VITE_LUMEN_PUBLISHABLE_KEY} // Adjust based on your bundler
      userId={userId}
      loginRedirectUrl="/login"
    />
  );
}

9. Entitlements: Frontend vs Backend

Lumen allows you to check feature access in two places. It's important to understand where and why to use each.

ContextPurposeSecurity
FrontendUX/UI: Hide buttons, show upgrade prompts, display usage bars.Not secure (users can bypass with DevTools).
BackendEnforcement: Prevent API access, block database writes, track reliable usage.Secure. This is the source of truth.

Note: You rarely need to check "Does user have a subscription?". Instead, check "Does user have Feature X?". This decouples your code from plan names.

Frontend (UX)

Use these to improve user experience. They don't replace backend checks.

Hide/Gate Content

import { useFeature } from "@getlumen/react";

const { entitled } = useFeature(userId, "access-to-live-customer-service");

if (!entitled) return null;

return <LiveChatWidget />;
import { FeatureGate, Paywall } from "@getlumen/react";

// Simple: Render null if not entitled
<FeatureGate
  userId={userId}
  feature="access-to-live-customer-service"
>
  <LiveChatWidget />
</FeatureGate>

// Upgrade Prompt: Show a paywall if not entitled
<Paywall
  userId={userId}
  feature="access-to-live-customer-service"
  onUpgrade={() => navigate('/pricing')}
>
  <LiveChatWidget />
</Paywall>

Show Usage

import { useUsageQuota } from "@getlumen/react";

const { used, limit, percentage } = useUsageQuota(userId, "api-calls");
<UsageBadge
  featureSlug="api-calls"
  label="API Calls"
/>

Backend (Security)

This is where you enforce limits. Always check entitlements here before performing sensitive actions.

from lumen import send_event, is_feature_entitled

@app.route('/api/expensive-operation', methods=['POST'])
async def expensive_operation():
    user_id = get_user_id()

    # 1. SECURITY CHECK: Does the user have the feature?
    if not await is_feature_entitled(feature="api-calls", user_id=user_id):
        return {'error': 'Upgrade required'}, 403

    # 2. Execute Business Logic
    result = do_expensive_work()

    # 3. Track Usage
    await send_event(name="api-calls", user_id=user_id)

    return {'success': True, 'result': result}
from lumen import send_event, is_feature_entitled

@app.post('/api/expensive-operation')
async def expensive_operation(request: Request):
    user_id = get_user_id(request)

    # 1. SECURITY CHECK: Does the user have the feature?
    if not await is_feature_entitled(feature="api-calls", user_id=user_id):
        raise HTTPException(status_code=403, detail="Upgrade required")

    # 2. Execute Business Logic
    result = await do_expensive_work()

    # 3. Track Usage
    await send_event(name="api-calls", user_id=user_id)

    return {'success': True, 'result': result}
from lumen import send_event, is_feature_entitled
from django.http import JsonResponse

async def expensive_operation(request):
    user_id = str(request.user.id)

    # 1. SECURITY CHECK: Does the user have the feature?
    if not await is_feature_entitled(feature="api-calls", user_id=user_id):
        return JsonResponse({'error': 'Upgrade required'}, status=403)

    # 2. Execute Business Logic
    result = do_expensive_work()

    # 3. Track Usage
    await send_event(name="api-calls", user_id=user_id)

    return JsonResponse({'success': True, 'result': result})

10. Test Checkout

Run both your frontend and backend, then navigate to your pricing page. Use these test cards to complete a purchase:

  • Card: 4242 4242 4242 4242
  • Expiry: Any future date (e.g. 12/34)
  • CVC: Any 3 digits (e.g. 123)

After checkout, verify the subscription:

  1. Lumen Dashboard: Subscriptions page
  2. Stripe Dashboard: Stripe Payments (Test Mode)
  • Card: 4242 4242 4242 4242
  • Expiry: 06/32 (must use this specific date)
  • CVC: 123 (must use this specific code)

After checkout, verify the subscription:

  1. Lumen Dashboard: Subscriptions page
  2. Dodo Dashboard: Dodo Payments Dashboard

Going to Production

You are currently using Test Mode keys. When you are ready to launch:

  1. Create a new Lumen Account (or use a separate team/workspace if available) to keep production data clean.
  2. Get your Live Mode keys from Stripe.
  3. Update your environment variables with the new Live keys.

Done!

Lumen billing is now fully integrated. You can test upgrades, downgrades, and cancellations.

Additional Python SDK Features

The Python SDK provides additional utilities for advanced use cases:

Seat Management (Team/Organization Plans)

from lumen import add_seat, remove_seat, get_seat_usage

# Add user to organization
await add_seat(
    new_user_id="user_456",
    organisation_user_id="org_owner_123",
    metadata={"role": "developer"}
)

# Remove user
await remove_seat(
    removed_user_id="user_456",
    organisation_user_id="org_owner_123"
)

# Check seat usage
usage = await get_seat_usage(customer_id="cust_123")

Subscription Status

from lumen import get_subscription_status, get_customer_overview

# Check active subscription
status = await get_subscription_status(user_id="user_123")
# Returns: {"hasActiveSubscription": True, "customer": {...}}

# Retrieve customer details
overview = await get_customer_overview(user_id="user_123")

Event Tracking with Idempotency

from lumen import send_event

# Track with idempotency key to prevent duplicates
await send_event(
    name="api_call",
    value=1,
    user_id="user_123",
    idempotency_key="unique_key_123"
)

Next Steps