# How to use Stripe API with Python

- Tool: Stripe
- Difficulty: Beginner
- Time required: 15 minutes
- Compatibility: Stripe Python SDK v7+, Python 3.8+, Flask 3+ or Django 4+
- Last updated: March 2026

## TL;DR

This guide covers integrating Stripe with Python using Flask and Django. Install the stripe package with pip, configure your API keys via environment variables, create customers, process payments with PaymentIntents, and handle webhooks with signature verification. All examples use test mode with the 4242424242424242 test card and amounts in cents.

## Getting Started with the Stripe API in Python

The Stripe Python SDK provides a clean, Pythonic interface to the Stripe REST API. It supports both Flask and Django, with built-in error handling and webhook signature verification. This guide covers both frameworks, starting with Flask for simplicity and showing Django equivalents. All examples use test mode so you can follow along without processing real payments.

## Before you start

- Python 3.8 or later installed
- A Stripe account with test API keys
- pip for package installation
- Basic familiarity with Flask or Django

## Step-by-step guide

### 1. Install the Stripe SDK and Flask

Install the required packages with pip.

```
# Install Stripe and Flask
pip install stripe flask python-dotenv

# Or for Django:
# pip install stripe django python-dotenv
```

**Expected result:** Stripe, Flask, and dotenv packages are installed.

### 2. Configure Stripe with environment variables

Create a .env file and configure the Stripe client in your application.

```
# .env
# STRIPE_SECRET_KEY=sk_test_your_key_here
# STRIPE_PUBLISHABLE_KEY=pk_test_your_key_here
# STRIPE_WEBHOOK_SECRET=whsec_your_secret_here

# app.py
import os
import stripe
from flask import Flask, request, jsonify
from dotenv import load_dotenv

load_dotenv()

stripe.api_key = os.environ.get('STRIPE_SECRET_KEY')
stripe.api_version = '2024-12-18.acacia'

app = Flask(__name__)

if not stripe.api_key:
    raise ValueError('STRIPE_SECRET_KEY environment variable is not set')

print('Stripe initialized with API version:', stripe.api_version)
```

**Expected result:** The Stripe client is configured with your test secret key and pinned API version.

### 3. Create a customer and PaymentIntent with Flask

Build Flask routes for creating customers and processing payments.

```
@app.route('/api/customers', methods=['POST'])
def create_customer():
    try:
        data = request.get_json()
        customer = stripe.Customer.create(
            email=data.get('email'),
            name=data.get('name'),
            metadata={'app_user_id': data.get('userId', '')},
        )
        return jsonify({'customerId': customer.id})
    except stripe.error.StripeError as e:
        return jsonify({'error': str(e)}), 400


@app.route('/api/create-payment-intent', methods=['POST'])
def create_payment_intent():
    try:
        data = request.get_json()
        intent = stripe.PaymentIntent.create(
            amount=data['amount'],  # Amount in cents
            currency='usd',
            customer=data.get('customerId'),
            automatic_payment_methods={'enabled': True},
            metadata={'order_id': data.get('orderId', '')},
        )
        return jsonify({'clientSecret': intent.client_secret})
    except stripe.error.StripeError as e:
        return jsonify({'error': str(e)}), 400
```

**Expected result:** Flask routes create Stripe customers and PaymentIntents, returning the client_secret for frontend confirmation.

### 4. Handle webhooks with signature verification

Verify webhook signatures using the raw request body. This is critical for security.

```
@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_data()  # Raw body bytes
    sig_header = request.headers.get('Stripe-Signature')
    webhook_secret = os.environ.get('STRIPE_WEBHOOK_SECRET')

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, webhook_secret
        )
    except ValueError:
        return 'Invalid payload', 400
    except stripe.error.SignatureVerificationError:
        return 'Invalid signature', 400

    # Handle the event
    if event['type'] == 'payment_intent.succeeded':
        intent = event['data']['object']
        print(f"Payment succeeded: {intent['id']} (${intent['amount'] / 100})")
    elif event['type'] == 'payment_intent.payment_failed':
        intent = event['data']['object']
        print(f"Payment failed: {intent['id']}")

    return jsonify({'received': True})
```

**Expected result:** Webhook signatures are verified and events are processed securely.

### 5. Handle Stripe errors properly

Stripe raises specific exception types for different error scenarios.

```
def handle_stripe_error(e):
    if isinstance(e, stripe.error.CardError):
        return jsonify({
            'error': e.user_message,
            'code': e.code,
            'decline_code': e.json_body.get('error', {}).get('decline_code'),
        }), 402
    elif isinstance(e, stripe.error.RateLimitError):
        return jsonify({'error': 'Too many requests. Please retry.'}), 429
    elif isinstance(e, stripe.error.InvalidRequestError):
        return jsonify({'error': str(e)}), 400
    elif isinstance(e, stripe.error.AuthenticationError):
        return jsonify({'error': 'Payment service configuration error.'}), 500
    elif isinstance(e, stripe.error.APIConnectionError):
        return jsonify({'error': 'Payment service temporarily unavailable.'}), 503
    else:
        return jsonify({'error': 'An unexpected error occurred.'}), 500
```

**Expected result:** Each Stripe error type is handled with an appropriate HTTP status code and user-friendly message.

### 6. Django integration alternative

If you use Django instead of Flask, here is the equivalent view setup.

```
# views.py (Django)
import json
import stripe
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
import os

stripe.api_key = os.environ.get('STRIPE_SECRET_KEY')

@require_POST
def create_payment_intent(request):
    data = json.loads(request.body)
    try:
        intent = stripe.PaymentIntent.create(
            amount=data['amount'],
            currency='usd',
            automatic_payment_methods={'enabled': True},
        )
        return JsonResponse({'clientSecret': intent.client_secret})
    except stripe.error.StripeError as e:
        return JsonResponse({'error': str(e)}, status=400)

@csrf_exempt
@require_POST
def webhook(request):
    payload = request.body  # Raw bytes
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, os.environ.get('STRIPE_WEBHOOK_SECRET')
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return JsonResponse({'error': 'Invalid'}, status=400)

    if event['type'] == 'payment_intent.succeeded':
        print('Payment succeeded:', event['data']['object']['id'])

    return JsonResponse({'received': True})
```

**Expected result:** Django views handle PaymentIntent creation and webhook verification with the same Stripe SDK.

## Complete code example

File: `app.py`

```python
import os
import stripe
from flask import Flask, request, jsonify
from dotenv import load_dotenv

load_dotenv()

stripe.api_key = os.environ.get('STRIPE_SECRET_KEY')
stripe.api_version = '2024-12-18.acacia'

app = Flask(__name__)

@app.route('/api/config')
def get_config():
    return jsonify({
        'publishableKey': os.environ.get('STRIPE_PUBLISHABLE_KEY'),
    })

@app.route('/api/customers', methods=['POST'])
def create_customer():
    try:
        data = request.get_json()
        customer = stripe.Customer.create(
            email=data.get('email'),
            name=data.get('name'),
        )
        return jsonify({'customerId': customer.id})
    except stripe.error.StripeError as e:
        return jsonify({'error': str(e)}), 400

@app.route('/api/create-payment-intent', methods=['POST'])
def create_payment_intent():
    try:
        data = request.get_json()
        intent = stripe.PaymentIntent.create(
            amount=data['amount'],
            currency='usd',
            customer=data.get('customerId'),
            automatic_payment_methods={'enabled': True},
        )
        return jsonify({'clientSecret': intent.client_secret})
    except stripe.error.StripeError as e:
        return jsonify({'error': str(e)}), 400

@app.route('/api/payments/<payment_id>')
def get_payment(payment_id):
    try:
        intent = stripe.PaymentIntent.retrieve(payment_id)
        return jsonify({
            'id': intent.id,
            'status': intent.status,
            'amount': intent.amount,
        })
    except stripe.error.StripeError as e:
        return jsonify({'error': str(e)}), 400

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_data()
    sig = request.headers.get('Stripe-Signature')
    try:
        event = stripe.Webhook.construct_event(
            payload, sig, os.environ.get('STRIPE_WEBHOOK_SECRET')
        )
    except (ValueError, stripe.error.SignatureVerificationError) as e:
        return str(e), 400

    if event['type'] == 'payment_intent.succeeded':
        print(f"Payment confirmed: {event['data']['object']['id']}")
    elif event['type'] == 'payment_intent.payment_failed':
        print(f"Payment failed: {event['data']['object']['id']}")

    return jsonify({'received': True})

if __name__ == '__main__':
    app.run(port=3000, debug=True)
```

## Common mistakes

- **Using request.json instead of request.get_data() for webhook verification in Flask** — undefined Fix: Webhook signature verification requires the raw request body. Use request.get_data() in Flask or request.body in Django.
- **Setting stripe.api_key to the publishable key** — undefined Fix: Use the secret key (sk_test_ or sk_live_) for stripe.api_key. The publishable key is for frontend use only.
- **Not handling stripe.error.StripeError exceptions** — undefined Fix: Wrap all Stripe API calls in try/except blocks catching stripe.error.StripeError or its subclasses.
- **Forgetting @csrf_exempt on Django webhook views** — undefined Fix: Stripe webhook requests do not include CSRF tokens. Add @csrf_exempt decorator to the webhook view in Django.

## Best practices

- Pin the API version with stripe.api_version to prevent unexpected changes
- Use environment variables for API keys — never hardcode them
- Use request.get_data() (Flask) or request.body (Django) for webhook signature verification
- Handle all Stripe error types with appropriate HTTP status codes
- Use test card 4242424242424242 with any future expiry and any CVC for testing
- Test webhooks locally with the Stripe CLI: stripe listen --forward-to localhost:3000/webhook
- Add metadata to PaymentIntents to link them to your internal order records

## Frequently asked questions

### Which Python version does the Stripe SDK require?

The Stripe Python SDK v7+ requires Python 3.6 or later. We recommend Python 3.10+ for the best experience.

### Can I use Stripe with FastAPI?

Yes. The Stripe SDK works with any Python web framework. For FastAPI, use Request.body() to get raw bytes for webhook verification and return JSONResponse for API responses.

### How do I use async/await with the Stripe Python SDK?

The Stripe Python SDK v7+ supports async operations natively. Use stripe.PaymentIntent.create_async() and await the result in async frameworks like FastAPI.

### What is the test card number for Stripe?

Use 4242424242424242 with any future expiry date and any 3-digit CVC. This always succeeds in test mode. Use 4000000000000002 for a declined card.

### Can RapidDev help with Python Stripe integrations?

Yes. RapidDev can build and maintain Stripe integrations in Python using Flask, Django, or FastAPI, including complex subscription billing and Connect marketplace flows.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-api-with-python
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-api-with-python
