Stripe Webhook Events: Real-Time Payment & Subscription Notifications
In this tutorial, you will learn about Stripe Webhook Events: Real. We cover key concepts, practical examples, and best practices to help you master this topic.
Stripe Webhooks send HTTP POST requests to your server when events happen — payments succeed, subscriptions update, invoices fail. Processing these reliably is essential for order fulfillment.
What You'll Learn
How to receive and verify Stripe webhooks, Process key events (checkout.session.completed, invoice events, subscription events), handle idempotency, and build event-driven fulfillment.
Why It Matters
Webhooks are Stripe's way of telling your server what happened. Without them, you won't know if a payment succeeded or failed. DodaTech processes 50+ webhook event types to keep billing synchronized.
Real-World Use
A customer completes checkout. Stripe sends checkout.session.completed webhook. Your handler verifies the signature, activates the subscription, sends a welcome email, and records the Transaction.
flowchart LR
A["Stripe\nEvent Occurs"] --> B["HTTP POST\nto Your Endpoint"]
B --> C["Verify\nSignature"]
C --> D{"Event\nType?"}
D -->|"checkout.session.completed"| E["Activate\nSubscription"]
D -->|"invoice.payment_failed"| F["Notify\nCustomer"]
D -->|"customer.subscription.deleted"| G["Downgrade\nAccount"]
style C fill:#fef3c7,stroke:#d97706
style D fill:#dbeafe,stroke:#2563eb
Webhook Handler
from flask import Flask, request, jsonify
import stripe
import os
app = Flask(__name__)
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
endpoint_secret = os.environ["STRIPE_WEBHOOK_SECRET"]
@app.route("/stripe/webhook", methods=["POST"])
def stripe_webhook():
payload = request.get_data(as_text=True)
sig_header = request.headers.get("Stripe-Signature")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError:
return jsonify({"error": "Invalid payload"}), 400
except stripe.error.SignatureVerificationError:
return jsonify({"error": "Invalid signature"}), 400
event_type = event["type"]
data = event["data"]["object"]
print(f"Received event: {event_type}")
handlers = {
"checkout.session.completed": handle_checkout_completed,
"payment_intent.succeeded": handle_payment_succeeded,
"payment_intent.payment_failed": handle_payment_failed,
"customer.subscription.updated": handle_subscription_updated,
"customer.subscription.deleted": handle_subscription_deleted,
"invoice.payment_succeeded": handle_invoice_paid,
"invoice.payment_failed": handle_invoice_failed,
"charge.dispute.created": handle_dispute,
}
handler = handlers.get(event_type)
if handler:
handler(data)
else:
print(f"Unhandled event: {event_type}")
return jsonify({"status": "ok"}), 200
Event Handlers
def handle_checkout_completed(session):
customer_email = session.get("customer_details", {}).get("email")
subscription_id = session.get("subscription")
metadata = session.get("metadata", {})
print(f"Checkout completed for {customer_email}")
print(f"Subscription: {subscription_id}")
if subscription_id:
activate_subscription(customer_email, subscription_id)
else:
fulfill_one_time_order(session)
def handle_subscription_updated(subscription):
status = subscription["status"]
customer = subscription["customer"]
print(f"Subscription {subscription['id']}: {status}")
if status == "active":
update_user_subscription(customer, subscription["id"], "active")
elif status == "past_due":
notify_payment_failed(customer)
def handle_invoice_failed(invoice):
customer_email = invoice.get("customer_email")
amount_due = invoice["amount_due"] / 100
attempt_count = invoice["attempt_count"]
print(f"Invoice payment failed for {customer_email}")
print(f"Amount: ${amount_due}, Attempt: {attempt_count}")
send_payment_failure_email(customer_email, attempt_count)
Testing Webhooks Locally
# Install Stripe CLI
# brew install stripe/stripe-cli/stripe
# Listen and forward to local server
stripe listen --forward-to localhost:5000/stripe/webhook
# Trigger test events
stripe trigger checkout.session.completed
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.updated
# Expected output:
# Got event: checkout.session.completed (session: cs_test_...)
# Forwarded to http://localhost:5000/stripe/webhook
# Got event: payment_intent.succeeded (intent: pi_test_...)
Common Mistakes
1. Not Verifying Signatures
Without signature verification, anyone can POST fake events. Always verify using stripe.Webhook.construct_event() with your endpoint secret.
2. Processing Events Synchronously
Slow processing causes Stripe to retry (timeout after 30 seconds). Acknowledge with 200 immediately, process asynchronously (queue).
3. Not Handling Event Idempotency
Stripe may deliver the same event multiple times. Use the event ID for deduplication. Stripe also provides idempotency keys in the request.
4. Ignoring Webhook Delivery Failures
If your endpoint returns non-200, Stripe retries for up to 3 days. Check the Stripe Dashboard > Webhooks > Failed attempts regularly.
5. Mixing Test and Live Webhooks
Test mode events go to test endpoints, live events to live. Configure separate endpoints or check the livemode field in the event payload.
Practice Questions
- How do you verify a webhook signature?
- What is the most reliable way to fulfill orders after payment?
- How do you handle duplicate webhook events?
- What events should you listen for subscription management?
Answers:
- Use
stripe.Webhook.construct_event(payload, sig_header, secret). It validates the HMAC signature and returns the verified event. - Use the
checkout.session.completedwebhook event. It's the most reliable indicator that payment was collected. - Store processed event IDs (from
event.id). Check before processing. Stripe also includes an idempotency key (Idempotency-Keyheader). customer.subscription.updated,customer.subscription.deleted,invoice.payment_succeeded,invoice.payment_failed.
Challenge: Build a complete webhook handler: verify signature, dispatch to type-specific handlers, implement event deduplication, process checkout completions (activate subscription), handle invoice failures (notify customer), and log all events with timestamps.
FAQ
Mini Project
Build a robust webhook system: local Stripe CLI forwarding, signature verification, event dispatcher with 10+ handler functions, event deduplication (store processed event IDs), async processing with a task queue, and a monitoring dashboard showing webhook health.
What's Next
Customer Portal — let customers manage their billing without contacting support.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro