HyperSaaS
BackendSubscriptions

Webhooks

Stripe webhook handling for subscription and product sync.

HyperSaaS processes Stripe webhooks to keep the local database in sync with Stripe's state.

Webhook Endpoint

POST /api/subscriptions/webhook/

No authentication required — requests are verified using Stripe's webhook signature.

event = stripe.Webhook.construct_event(
    payload=request.body,
    sig_header=request.META["HTTP_STRIPE_SIGNATURE"],
    secret=STRIPE_WEBHOOK_SECRET,
)

Handled Events

Subscription Events

EventHandler
customer.subscription.createdCreate/update local Subscription + SubscriptionItems
customer.subscription.updatedUpdate subscription fields and items
customer.subscription.deletedUpdate subscription status to cancelled/ended

Processing:

  1. Extract subscription data from event payload
  2. Find StripeUser by customer ID
  3. Update/create Subscription record with all fields (period, status, trial, cancellation)
  4. Extract workspace_id from subscription metadata
  5. Update associated Workspace with subscription reference
  6. Delete existing SubscriptionItem records and recreate from webhook data

Product Events

EventHandler
product.createdCreate local Product + Feature mappings
product.updatedUpdate Product + sync Feature mappings
product.deletedUpdate Product (mark inactive)

Feature Sync:

Features are read from Stripe Product metadata:

Stripe Product metadata:
  features: "chat rag export analytics"

The handler:

  1. Splits the space-delimited feature string
  2. Creates Feature records for any new feature IDs
  3. Creates ProductFeature associations
  4. Removes orphaned associations (features no longer in metadata)

Price Events

EventHandler
price.createdCreate local Price record
price.updatedUpdate Price fields
price.deletedUpdate Price (mark inactive)

Frequency Calculation:

Stripe's price.recurring is converted to a freq string:

# Stripe: {"interval": "month", "interval_count": 1}
# Local:  freq = "month_1"

# Stripe: {"interval": "year", "interval_count": 1}
# Local:  freq = "year_1"

Webhook Setup

1. Configure in Stripe Dashboard

Go to Developers → Webhooks and add an endpoint:

URL: https://your-domain.com/api/subscriptions/webhook/

Select these events:

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • product.created
  • product.updated
  • product.deleted
  • price.created
  • price.updated
  • price.deleted

2. Set Webhook Secret

Copy the webhook signing secret and add it to your environment:

STRIPE_WEBHOOK_SECRET=whsec_...

3. Local Testing with Stripe CLI

# Forward webhook events to local server
stripe listen --forward-to localhost:8000/api/subscriptions/webhook/

# Trigger test events
stripe trigger customer.subscription.created

Event Validation

All webhook payloads are validated with Pydantic models before processing:

class StripeEvent(BaseModel):
    id: str
    type: str
    data: dict

    # Routes to specific Pydantic models based on event type
    # e.g., customer.subscription.* → StripeSubscription
    # e.g., product.* → StripeProduct

Invalid payloads are rejected with appropriate error logging.

Idempotency

Webhook handlers use update_or_create patterns, making them safe to replay. If Stripe sends the same event twice (which it can during retries), the handler produces the same result.

Error Handling

ScenarioResponse
Invalid signature400 Bad Request
Unhandled event type200 OK (acknowledged but ignored)
Processing error500 (Stripe will retry)
Missing StripeUserLogged as warning, event skipped

On this page