Recurring events and subscriptions

Hi,

I would like to set up an automated system that automatically updates certain values in the database each month. I am using a recurring event on the ‘user’ thing and the Stripe plugin to verify if users still have their subscriptions. However, due to the short delay, I am concerned about potential glitches. For instance, if a user does have an active subscription but Stripe takes a bit longer to update (even a few seconds could be too long), this could cause issues. Is there a better or more efficient way to manage this? Thanks a lot!

You should use webhooks. A recurring event asks Stripe every month whether the user’s subscription is active. A webhook is a message sent from Stripe to Bubble at any time that will tell you if the subscription changes.

2 Likes


I’m seeing everything except when the subscription continues (after the first month). Is it the same as ‘customer.subscription.created’ ?
Also, should I put everything in one API workflow or create one for each?
Thank you :slight_smile:

subscription.updated is the one you care about

I’m sorry, but I’m not very familiar with this. I tried it in test mode, and it was triggered when the subscription was created, when I cancelled it, and when I switched subscriptions, so that’s great. However, the amount of data received is overwhelming. What’s important in the response? Have you used it before?

If this is what you’re trying to do, then the subscription’s status would be important and if the status isn’t active you might change the user’s subscription in Bubble so they can’t access whatever they had access to before.

Idk i’m just starting to set the subscriptions up, one last thing: is there a variable or status idk, to know what the request is about, what changed either a subscription was created, switched, or cancelled ? I guess it’s all I need to update the user’s values on the app.

{
  "id": "evt_1OHplDGlVAcKxVHnILTNVErL",
  "object": "event",
  "api_version": "2022-11-15",
  "created": 1701271990,
  "data": {
    "object": {
      "id": "sub_1OHpM6GlVAcKxVHnej59alWH",
      "object": "subscription",
      "application": null,
      "application_fee_percent": null,
      "automatic_tax": {
        "enabled": false
      },
      "billing_cycle_anchor": 1701270434,
      "billing_thresholds": null,
      "cancel_at": null,
      "cancel_at_period_end": false,
      "canceled_at": null,
      "cancellation_details": {
        "comment": null,
        "feedback": null,
        "reason": null
      },
      "collection_method": "charge_automatically",
      "created": 1701270434,
      "currency": "eur",
      "current_period_end": 1703862434,
      "current_period_start": 1701270434,
      "customer": "cus_P61O6C1uHjGw1W",
      "days_until_due": null,
      "default_payment_method": "pm_1OHpM5GlVAcKxVHnYqFCe9sI",
      "default_source": null,
      "default_tax_rates": [],
      "description": null,
      "discount": null,
      "ended_at": null,
      "items": {
        "object": "list",
        "data": [
          {
            "id": "si_P61OcTElV7b2fP",
            "object": "subscription_item",
            "billing_thresholds": null,
            "created": 1701270434,
            "metadata": {},
            "plan": {
              "id": "curious-live",
              "object": "plan",
              "active": true,
              "aggregate_usage": null,
              "amount": 699,
              "amount_decimal": "699",
              "billing_scheme": "per_unit",
              "created": 1701269502,
              "currency": "eur",
              "interval": "month",
              "interval_count": 1,
              "livemode": false,
              "metadata": {},
              "nickname": null,
              "product": "prod_P619gjBKZMZD6I",
              "tiers_mode": null,
              "transform_usage": null,
              "trial_period_days": null,
              "usage_type": "licensed"
            },
            "price": {
              "id": "curious-live",
              "object": "price",
              "active": true,
              "billing_scheme": "per_unit",
              "created": 1701269502,
              "currency": "eur",
              "custom_unit_amount": null,
              "livemode": false,
              "lookup_key": null,
              "metadata": {},
              "nickname": null,
              "product": "prod_P619gjBKZMZD6I",
              "recurring": {
                "aggregate_usage": null,
                "interval": "month",
                "interval_count": 1,
                "trial_period_days": null,
                "usage_type": "licensed"
              },
              "tax_behavior": "unspecified",
              "tiers_mode": null,
              "transform_quantity": null,
              "type": "recurring",
              "unit_amount": 699,
              "unit_amount_decimal": "699"
            },
            "quantity": 1,
            "subscription": "sub_1OHpM6GlVAcKxVHnej59alWH",
            "tax_rates": []
          }
        ],
        "has_more": false,
        "total_count": 1,
        "url": "/v1/subscription_items?subscription=sub_1OHpM6GlVAcKxVHnej59alWH"
      },
      "latest_invoice": "in_1OHpM6GlVAcKxVHn1lMFFCwh",
      "livemode": false,
      "metadata": {},
      "next_pending_invoice_item_invoice": null,
      "on_behalf_of": null,
      "pause_collection": null,
      "payment_settings": {
        "payment_method_options": null,
        "payment_method_types": null,
        "save_default_payment_method": "off"
      },
      "pending_invoice_item_interval": null,
      "pending_setup_intent": null,
      "pending_update": null,
      "plan": {
        "id": "curious-live",
        "object": "plan",
        "active": true,
        "aggregate_usage": null,
        "amount": 699,
        "amount_decimal": "699",
        "billing_scheme": "per_unit",
        "created": 1701269502,
        "currency": "eur",
        "interval": "month",
        "interval_count": 1,
        "livemode": false,
        "metadata": {},
        "nickname": null,
        "product": "prod_P619gjBKZMZD6I",
        "tiers_mode": null,
        "transform_usage": null,
        "trial_period_days": null,
        "usage_type": "licensed"
      },
      "quantity": 1,
      "schedule": null,
      "start_date": 1701270434,
      "status": "active",
      "test_clock": null,
      "transfer_data": null,
      "trial_end": null,
      "trial_settings": {
        "end_behavior": {
          "missing_payment_method": "create_invoice"
        }
      },
      "trial_start": null
    },
    "previous_attributes": {
      "cancel_at": 1703862434,
      "cancel_at_period_end": true,
      "canceled_at": 1701270585,
      "cancellation_details": {
        "feedback": "unused",
        "reason": "cancellation_requested"
      }
    }
  },
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": null,
    "idempotency_key": "eb23995f-7303-41ae-930c-b706ea7b028c"
  },
  "type": "customer.subscription.updated"
}

It’s huge :smiling_face_with_tear:

So? Only use the information you need…

I’m lost… if it’s cancelled I can see the status on cancelled, but when it’s active how do I know if the subscription continued or if it’s a new one or if the guy switched subscription directly or he just changed his credit card etc…? Really sorry if it’s obvious…

Hey @gene.richez ,
So for all these cases you will have to listen to different events and handle them separately in bubble, Let’s check them on by one:

  1. Cancelled: customer.subscription.deletedand in payload check the status cancelled
  2. Subscription Continue (I assume you meant moved from trial to paid): customer.subscription.updated and in payload check status active and trial_end < current datetime
  3. New Subscription: customer.subscription.createdand check customer in payload.
  4. Switched Subscription: customer.subscription.updatedand in payload compare items and check previous_attributes, you can tweak logic as per your usecase.
  5. Payment Method Changed: customer.updated
1 Like

Thank you so much.
By ‘Subscription Continue’, I mean that a month has passed and he has paid for the second month, etc., to update each month.
Again you’re a savior :slight_smile: thanks

I always have a Billing Account data type. This contains:

Stripe customer ID
Stripe subscription ID
Any credits associated with the account
Subscription status
Subscription renewal date
Plan (most likely an option set, possibly a data type if the app is for a client)

These are the stuff you most likely care about.

1 Like

ok nice, it’s not necessary isn’t it ?

Not necessary but possible helpful, particularly for SaaS.

By ‘Subscription Continue’, I mean that a month has passed and he has paid for the second month, etc., to update each month.

How should I proceed to make that happen?

Hey @gene.richez :wave:

If you are using the native Bubble plugin for Stripe, Bubble takes care of the webhooks for you. You just need to check that the Current User's Stripe Subscription Status is active. No need for setting up your webhooks. Does that help at all? :blush:

Editor: testApp42wCleanDB | Bubble Editor

1 Like

Alright I see this is helpful, but how can I update some values each month using the Stripe plugin? That’s why I went for webhooks…
To make it clear, I need to trigger actions each time:

  • Subscription start
  • Subscription goes on (second month, third month, …)
  • Subscription is cancelled
  • Subscription is changed (through the Portal by switching plan)

I can’t manage everything just through conditions…

Yeah, the ‘active’ status would be for making sure they actually have an active subscription. If you are trying to send emails when each of these events occur, then a webhook should work just fine. The ‘active’ part is great for making sure they can’t access part of your site unless they are a paid subscriber. I have seen in the past that the webhooks when becoming a subscriber has issues. Worth a shot though. :raised_hands:

+1 on that.

It can particularly be an issue if you don’t create the Stripe customer before creating the checkout session. What I did at one point was listen for the checkout session completed webhook and the subscription created webhooks. I’d get the user’s unique ID from the checkout session completed webhook and update it in Bubble, and then ideally the subscription created webhook would find the relevant user in my database by searching for users with that customer ID. Trouble is, the subscription webhook came before the checkout session complete webhook so it didn’t work. Now I always create and save customer ID before making a checkout session out of an abundance of caution…

2 Likes