This guide explains how to integrate and handle Stripe webhooks for subscription management in Transcript Create.
Transcript Create uses Stripe webhooks to automatically update user subscription status when:
- A user completes checkout
- A subscription is created or updated
- A subscription is canceled or expires
POST /stripe/webhook
This endpoint receives and processes webhook events from Stripe.
In your Stripe Dashboard:
- Go to Developers → Webhooks
- Click Add endpoint
- Enter your webhook URL:
https://api.example.com/stripe/webhook - Select events to listen for:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deleted
- Copy the webhook signing secret
Add these to your .env file:
# Stripe API configuration
STRIPE_API_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Price IDs from Stripe Dashboard
STRIPE_PRICE_PRO_MONTHLY=price_...
STRIPE_PRICE_PRO_YEARLY=price_...
# Checkout URLs
STRIPE_SUCCESS_URL=https://app.example.com/pricing?success=1
STRIPE_CANCEL_URL=https://app.example.com/pricing?canceled=1
# Plan configuration
PRO_PLAN_NAME=proTriggered when a user successfully completes checkout.
What it does:
- Identifies the customer by Stripe customer ID
- Updates user's plan to "pro"
- Sets subscription status to "active"
Triggered when a new subscription is created.
What it does:
- Sets user plan based on subscription status
- Active or trialing subscriptions → "pro" plan
- Other statuses → no plan change
Triggered when subscription status changes (e.g., payment updated, plan changed).
What it does:
- Updates user plan based on new subscription status
- Handles transitions between active/inactive states
Triggered when a subscription is canceled or expires.
What it does:
- Sets user plan back to "free"
- Sets subscription status to "canceled"
Stripe signs all webhook events. The API automatically verifies signatures:
if settings.STRIPE_WEBHOOK_SECRET:
event = stripe.Webhook.construct_event(
payload,
signature_header,
settings.STRIPE_WEBHOOK_SECRET
)If verification fails, returns 400 Bad Request.
When processing events, the API:
- Extracts customer ID from the event
- Queries the database for user with matching
stripe_customer_id - Updates the user record:
planfield (free/pro)stripe_subscription_statusfieldupdated_attimestamp
All webhook requests return:
{
"received": true
}This acknowledges receipt to Stripe, even if processing fails (to avoid retries).
Install the Stripe CLI:
# Login to Stripe
stripe login
# Forward webhooks to local server
stripe listen --forward-to http://localhost:8000/stripe/webhook
# Trigger test events
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deletedThe CLI displays the webhook signing secret - add it to your .env:
STRIPE_WEBHOOK_SECRET=whsec_test_...Use Stripe Dashboard to send test webhooks:
- Go to Developers → Webhooks
- Click on your endpoint
- Click Send test webhook
- Select event type and send
Send a test webhook with curl:
# Get a sample event from Stripe CLI or docs
curl -X POST https://api.example.com/stripe/webhook \
-H "Content-Type: application/json" \
-H "Stripe-Signature: t=timestamp,v1=signature" \
-d @test_event.json- User clicks "Upgrade to Pro" on frontend
- Frontend calls
POST /billing/checkout-session - API creates Stripe checkout session
- User redirects to Stripe checkout page
- User completes payment
- Stripe sends
checkout.session.completedwebhook - API updates user plan to "pro"
- User redirects back to success page
- User accesses billing portal via
GET /billing/portal - User cancels subscription in Stripe portal
- Stripe sends
customer.subscription.deletedwebhook - API updates user plan back to "free"
400 Bad Request - Invalid signature
{
"error": "validation_error",
"message": "Invalid webhook signature: ...",
"details": {}
}400 Bad Request - Malformed event
{
"error": "validation_error",
"message": "Webhook error: ...",
"details": {}
}503 Service Unavailable - Stripe not configured
{
"error": "external_service_error",
"message": "Stripe billing is not configured",
"details": {
"service": "Stripe"
}
}Monitor webhook deliveries in Stripe Dashboard:
- Go to Developers → Webhooks
- Click on your endpoint
- View delivery history and status
- Retry failed deliveries if needed
The API logs all webhook events:
{
"level": "info",
"message": "Stripe webhook received",
"event_type": "customer.subscription.updated",
"customer_id": "cus_...",
"timestamp": "2025-10-25T10:30:00Z"
}If webhook processing fails:
- Stripe automatically retries with exponential backoff
- Check application logs for errors
- Fix the issue
- Use Stripe Dashboard to retry failed events
Always verify webhook signatures to ensure events come from Stripe:
# ✅ Good - Verifies signature
event = stripe.Webhook.construct_event(
payload, signature, webhook_secret
)
# ❌ Bad - Trusts unverified data
event = json.loads(payload)Webhook handlers should be idempotent (safe to run multiple times):
- Use
UPDATEinstead ofINSERTwhere possible - Check existing state before making changes
- Handle duplicate events gracefully
- Use HTTPS in production
- Don't expose webhook secret in logs or errors
- Rate limit webhook endpoint if needed
- Monitor for suspicious activity
Cause: Webhook secret mismatch
Solution:
- Copy correct secret from Stripe Dashboard
- Update
STRIPE_WEBHOOK_SECRETin.env - Restart API server
Cause: Webhook references customer not in database
Solution: Ensure customer ID is set when creating Stripe customers:
customer = stripe.Customer.create(
email=user_email,
metadata={"user_id": str(user_id)}
)Cause: Event not handled or database transaction failed
Solution:
- Check application logs for errors
- Verify event type is in handled list
- Check database connection
- Retry failed event from Stripe Dashboard
POST /billing/checkout-session
Creates a Stripe checkout session for Pro subscription.
GET /billing/portal
Returns URL to Stripe customer portal for managing subscriptions.
- Getting Started - Basic API usage
- Authentication - OAuth and session management
- API Reference - Complete endpoint documentation
- Stripe Webhooks Documentation