Stripe Webhooks aren't reliably catching charges

Hi,

I was hoping someone might have some insights…

I’m using Stripe’s Subscriptions/Billing system with Connected Accounts.

I have a webhook set up to get charge.succeeded, but it doesn’t reliably catch it.

Sometimes it does and other times it doesn’t.

Does anyone know why that might be or how to fix it? Has anyone experienced this?

Thanks in advance.

Jeff

Try using invoice.payment_succeeded

The problem with invoice.payment_succeeded is that it doesn’t appear to have the charge id, ch_

Are you telling me that invoice.payment_succeeded sends more reliably than charge.succeeded?

Yes, it has a ch_ id.

I’m using invoice.payment_succeeded to track subscription on users using customer_id that I create before adding a subscription.

You have to ping the webhook even in test-mode to see the real fields that stripe sends, cause if you test using stripe test webhook shows incomplete fields.

This is one of my real webhook ping coming from stripe to my app. (I added on some fields cause this is real data).

I’ve marked some of the important fields may you need to track properly a subscription creation/renewal.

{
“id”: “evt_1DXfup…”,
“object”: “event”,
“api_version”: “2018-07-27”,
“created”: 15425…,
“data”: {
“object”: {
“id”: “in_1DXfuoA…”,
“object”: “invoice”,
“amount_due”: 32900,
“amount_paid”: 32900,
“amount_remaining”: 0,
“application_fee”: null,
“attempt_count”: 1,
“attempted”: true,
“auto_advance”: false,
“billing”: “charge_automatically”,
“billing_reason”: “subscription_update”,
"charge": “ch_1DXfuoANJ5…”,
“closed”: true,
“currency”: “usd”,
"customer": “cus_Dyt7jr…”,
“date”: 1542…,
“default_source”: null,
“description”: null,
“discount”: null,
“due_date”: null,
“ending_balance”: 0,
“finalized_at”: 1542…,
“forgiven”: false,
“hosted_invoice_url”: “https://pay.stripe.com/invoice/…”,
“invoice_pdf”: “https://pay.stripe.com/invoice/…”,
“lines”: {
“object”: “list”,
“data”: [
{
“id”: “sli_fd5…”,
“object”: “line_item”,
“amount”: 32900,
“currency”: “usd”,
“description”: “1 × …”,
“discountable”: true,
“livemode”: true,
“metadata”: {
},
“period”: {
"end": 1574043698,
"start": 1542507698
},
“plan”: {
"id": “plan_DN…”,
“object”: “plan”,
“active”: true,
“aggregate_usage”: null,
“amount”: 32900,
“billing_scheme”: “per_unit”,
“created”: 1533…,
“currency”: “usd”,
“interval”: “year”,
“interval_count”: 1,
“livemode”: true,
“metadata”: {
},
“nickname”: “…”,
"product": “prod_DNy…”,
“tiers”: null,
“tiers_mode”: null,
“transform_usage”: null,
“trial_period_days”: null,
“usage_type”: “licensed”
},
“proration”: false,
“quantity”: 1,
"subscription": “sub_DzfFne…”,
“subscription_item”: “si_DzfFYn…”,
“type”: “subscription”
}
],
“has_more”: false,
“total_count”: 1,
“url”: “/v1/invoices/…”
},
“livemode”: true,
“metadata”: {
},
“next_payment_attempt”: null,
“number”: “8DD…”,
“paid”: true,
“payment_intent”: null,
“period_end”: 1542507698,
“period_start”: 1542507698,
“receipt_number”: null,
“starting_balance”: 0,
“statement_descriptor”: null,
“status”: “paid”,
“subscription”: “sub_Dzf…”,
“subtotal”: 32900,
“tax”: 0,
“tax_percent”: 0.0,
“total”: 32900,
“webhooks_delivered_at”: null
}
},
“livemode”: true,
“pending_webhooks”: 1,
“request”: {
“id”: “req_2V1…”,
“idempotency_key”: null
},
“type”: “invoice.payment_succeeded”
}

Thank you for that.

So then what’s the difference between
charge.succeeded
and
invoice.payment_succeeded

?

charge.succeeded occurs whenever a new charge is created and is successful. This normally is used to track standalone charges. If you use this event for subscriptions you can’t access data like subscription_id, start_date, end_date, plan_id which are FUNDAMENTAL to create a working subscription setup between stripe and your app.

invoice.payment_succeeded ONLY occurs whenever an invoice payment succeeds. This is extremely useful because this occurs on subscription payment and not on standalone. And includes data like subscription_id, start_date, end_date, plan_id… etc. The strong point of this event is that doesn’t matter if it’s the creation of the subscription or not like customer.subscription.created does. This event will work also for renewal date.

Hope this helps you.

2 Likes

Thank you for that clarification.

Indeed, i was doing a 2-step process of doing a Retrieve Invoice in order to get the subscriptionId…

If all of the charges on my site are from subscriptions, is there some scenario where the charge.succeeded might be missing things?

I’ve already answered that. charge.succeeded doesn’t contain subscription data like subscription_id, plan start/end date, plan_id…

You can use charge.succeded and send to your bubble the customer_id, find it in your DB and add the end_date manually (static). It’s another option. But you have to edit the end_points everytime you edit the plan in stripe.

Using the invoice.payment_succeeded you only need to configure one time.

Ok got it, thanks.

Maybe you can help me with this other issue as well - I’ve spent easily more than 40 hours on it and it works a good chunk of the time, and doesn’t work another chunk of the time.

My subscription system deals with donations - I’m tracking all scheduled ones in a table “scheduled_donation_component” in my system, which includes a variable lastActiveSubscriptionId, which gets saved to it right after i create a subscription.

image

I have it set so that the subscription gets created with trial_end=now, which initiates a payment as soon as the subscription is created.

I have this webhook set up to catch the invoice.payment_succeeded (as per what you wrote before):

I use a table called “payment” to store every payment/charge entry that occurs, and I’m creating one as the first step of the webhook coming in - as you can see, I’m pulling in a few pieces of information i need:

You can see here that the “payment” table entry gets the subscriptionId

image

It’s this next step that is causing me problems - I need to then reverse look up the subscriptionId to link it back to the scheduled_donation_component in my system.

So my problem is that sometimes this retrieves the scheduled_donation_component and sometimes it doesn’t. If it doesn’t, everything breaks.

Any thoughts on why this search might not work some of the time?

Added Note: In the “payment” table entry, I see the referenceSubscriptionId appear there 100% of the time - then I go and manually check and I see the same lastActiveSubscriptionId in that entry in the scheduled_donation_component table.

Any help would be appreciated.

Thanks in advance.

1 Like

Couldn’t you just do step 2 as part of step 1?

In other words in step 1, set lastActiveSubscriptionId = Search for scheduled_donation_components:first item (constraint > lastActiveSubscriptionId = Request Data’s object subscription)

You’ll always have better workflow consistency if you can avoid create/modify actions on the same thing.

See, i had tried that initially and it wasn’t getting the data… I was thinking that maybe if it was trying to do it in the same step, it was having problems fetching the data quickly enough and was missing it, so I separated it into a separate step…

Can you clarify why it would be more inclined to turn out worse with more create/modify actions on the same thing?

Note: I just tried setting it up that way - It didn’t get the scheduled_donation_component…

What field should get from stripe to scheduled_donation_component?

Another question: When the webhook doesn’t work: It doesn’t work completely or partially?

Is the scheduled_donation_component that you are retrieving getting created in the same workflow? That could account for the inconsistency as the workflow steps are not guaranteed to happen in order.

I need to get the subscriptionId from the stripe webhook to link it back to the scheduled_donation_component (the scheduled_donation_component contains all of the information I need in order to finish off the flow).

Well the webhook works - it’s that search step that tries to find the corresponding scheduled_donation_component.
When that step doesn’t work, many subsequent steps don’t (basically all the steps that require the information of that scheduled_donation_component - which is most of them).

  1. scheduled_donation_component created
  2. plan created
  3. seller customer created
  4. subscription created
    —Pause while stripe does it’s thing and initiates the subscription - since it starts “now”, it proceeds to do the first payment now
  5. invoice.payment_succeeded webhook gets triggered and my API Endpoint catches it
  6. The sequence above gets initiated

Is this a Bubble bug? Or something i’m doing wrong?

Okay, I read that as the scheduled_donation_component is created before this webhook is triggered.

I’m noticing Step 5 is also making changes to payment (at least I think it is.) You have to be very careful about triggering actions that create/modify the same data. Because Bubble will optimize things, it can trip on itself when modifying the same record. It’s just one of those things to avoid and your description of working sometimes, other times not, fits the mold…not to mention the pulling your hair out for 40 hours. :slight_smile:

Here’s a couple things that might help you restructure:

  • Have the main api endpoint call custom events, which seem to finish before moving down the chain.
  • Have the main api endpoint call/schedule other endpoints, but again make sure they are scheduled in a way to avoid modifying the same thing at the same time

If you still run into trouble, feel free to book an free intro session with me and we can look at it together.

Ken, that seemed to have done it!

Thanks! :slight_smile:

Do you know why Bubble seems to have trouble with things like this?
Do you know the various scenarios like this where Bubble is prone to having difficulties?

What if I have hundreds or thousands of these webhooks coming in within a hour… is this going to be a problem? and if so, do you know of anything i can do to mitigate it?

Thanks!

1 Like

“Why” is above my pay grade. :slight_smile: But I’m a firm believer that there are always reasons as to why things “are the way they are.”

I’ve had to deal with the web hook rapid fire situation as well. Solutions on top of those noted earlier depend on the situation. Sometimes it is just to space things out with scheduling using a random change to scheduled time. But sometimes I have gone so far as to set-up an external endpoint outside of bubble, that (long story short) only allows one edit at a time.

Is Bubble going to be unable to handle it?

Can you clarify what you mean here?