#naveed-web3auth_webhooks
1 messages ยท Page 1 of 1 (latest)
๐ Welcome to your new thread!
โฒ๏ธ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
โฑ๏ธ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.
๐ This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1460536746226745470
๐ Have more to share? Add more details, code, screenshots, videos, etc. below.
Hi there ๐ please give me a moment to catch up
This is the affected customer cus_T1Onjg2FvQ2D6L
their webhook history isnt long so you can see the events chronologically
I see, the two events you shared had different payment intent IDs
evt_3SltZmImFOsfVEtU3Vz1DNdV - pi_3SltZmImFOsfVEtU3bxKc42i
evt_3SltYjImFOsfVEtU1SxYgBwR - pi_3SltYjImFOsfVEtU1H6UQHU8
Both payment intents were created from different subscriptions. Based off the API logs, it looks like your server created two Subscriptions for this customer.
Give me awhile, I can share the API request ID that can be viewed in the Dashboard
For pi_3SltZmImFOsfVEtU3bxKc42i
- it was created from sub_1SltZlImFOsfVEtUGWr7SZSd
- this is the creation request for the subscription: https://dashboard.stripe.com/acct_1JUNJvImFOsfVEtU/logs/req_jMdmpNXkvkRvEj
- subscription was created at 2026-01-04 15:50:41 UTC
For pi_3SltYjImFOsfVEtU1H6UQHU8
- it was created from sub_1SltYiImFOsfVEtUFV0xyMxb
- this is the creation request for the subscription: https://dashboard.stripe.com/acct_1JUNJvImFOsfVEtU/logs/req_pxFZstJGpjJI9C
- subscription was created at 2026-01-04 15:49:36 UTC
The two subscriptions above came from a request made by your backend server. They weren't created by Stripe automatically. In this case, you would want to check your web payment flow about the possibility of a customer having two payment session available. For example, the customer may have two browser tabs opened for payment and mistakenly checkout twice.
hmm yes i see that the two requests are a minute apart
so its not related to the failure recovery email?
I have a few more examples of customers who faced this problem, e.g. cus_QHbLr9X8l4VzoA
in this case the gap between the subscription.created events is much larger
evt_1SepoqImFOsfVEtUDJXOmLcA and evt_1Seq7mImFOsfVEtU8jFl7Hyw
Around 10 minutes apart
I see that a payment failure email notification was sent to cus_T1Onjg2FvQ2D6L on Jan 4, but the email was never clicked. Did the customer said they updated their payment method via the email?
No i dont have any info on that
oh interesting so the email wasnt clicked? Do you know that?
I can check cus_QHbLr9X8l4VzoA too, give me a moment
Yes we have records whether the recipient interacted with the email. In our logs, I only see that the email on Jan 4 was delivered
Finally one more customer, this is the most serious case with 3 subscriptions created cus_TeCIvOK2oCX8Ei
the ones spaced only a few seconds apart are no doubt probably due to something perhaps being done on the frontend
idk if its multiple calls to my create subscription point or something
Could you recommend me a test card to try so that I can try and reproduce it on my end
All these flows seem to have in common is a card failure
Yup you can use any of the test cards here to simulate a decline: https://docs.stripe.com/testing#declined-payments
Afterwards, you can use these test cards that should create a successful charge: https://docs.stripe.com/testing#international-cards
So the most likely cause would be some sort of frontend issue eh
im not sure if multiple tabs would be the cause idk if a user would actually do something like that
Is your frontend returning any error message that notifies the user of the payment failure?
code is something like this
if (!this.creditCard && this.stripeElements) {
await billingModule.addCard({ stripeElements: this.stripeElements, teamId: this.selectedTeamId });
}
const subscriptionPlan = this.getPlanWithInterval(this.newPlan);
let result;
if (this.hasSubscription) {
result = await billingModule.updateSubscription({
teamId: this.selectedTeamId,
subscriptionPlan,
});
} else {
result = await billingModule.createSubscription({
teamId: this.selectedTeamId,
subscriptionPlan,
});
}
/**
* handling (double) 3d secure scenario
* Normally it should already be handled during card confirmSetup above
* But for some cards we face the situation where we need to confirm
* 3d secure again for the transaction itself
*
* Most other cards do not require further 3d secure confirmation after
* the initial card setup
*/
if (result.client_secret) {
const stripeClient = getStripeClient();
const { error } = await stripeClient.confirmPayment({
clientSecret: result.client_secret,
redirect: "if_required",
});
if (error) throw error;
}
๐ Hi there! I'm taking over for my colleague
Where does hasSubscription come from in this case?
But since aure asked about errors, it looks like you're throwing an exception if there's a payment failure?
Does this result in a message to the user? Or what happens here?
yes, but thus far it seems that it did not go to the throw
because those errors wouldve been sent to sentry
but i dont see any of those
has subscription is from server fetching on mount
hasSubscription() {
return Boolean(this.subscription && this.subscription?.customer && this.subscription.customer?.subscriptions.length > 0);
},
i think it would not have any subscription until success
as our webhook handler only writes to db on success
func (s *billingService) handleEventSubscriptionCreated(tx *gorm.DB, event stripe.Event) error {
// only process active status
subscriptionStatus := event.Data.Object["status"].(string)
if subscriptionStatus != string(stripe.SubscriptionStatusActive) && subscriptionStatus != string(stripe.SubscriptionStatusPastDue) {
return nil
}
so it would ignore if subscription status isnt active in webhook handler
What happens when you use a test card to simulate a failed payment?
im trying that here https://dashboard.stripe.com/acct_1JUNJvImFOsfVEtU/test/customers/cus_TmbgPP9DeaRKVw
Sign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.
should i refactor my payment code actually
am i doing things in a suboptimal way?
If you can see that your backend is sending multiple Subscription creation requests to Stripe, then that's not optimal
It's not clear to us exactly what sequence of events happens
If you're saying that you see problems with extra subscriptions being created when there's a payment failure, this is something you should be able to test using the test cards, and then see what API calls you're making
what i notice as a pattern thus far is customer is able to set up card. Then step 2 the payment fails.
Hmm i think what i need to do is maybe store the incomplete subscription* in memory somewhere
cuz i think that after payment failure when they try again with a working card a new sub gets created
i mean i dont think theres an issue with that per se
we can just let the older incomplete sub to expire naturally
idk but that payment intent attached to that previous sub also gets confirmed
If a payment fails, you should just be showing the error.message you get from confirming the payment. The customer should then be able to retry. You don't need to create a new Subscription or Payment Intent
async confirmSubscription() {
try {
this.paymentError = "";
this.confirmingSubscription = true;
if (!this.creditCard && this.stripeElements) {
await billingModule.addCard({ stripeElements: this.stripeElements, teamId: this.selectedTeamId });
}
const subscriptionPlan = this.getPlanWithInterval(this.newPlan);
let result;
if (this.hasSubscription) {
result = await billingModule.updateSubscription({
teamId: this.selectedTeamId,
subscriptionPlan,
});
} else {
result = await billingModule.createSubscription({
teamId: this.selectedTeamId,
subscriptionPlan,
});
}
/**
* handling (double) 3d secure scenario
* Normally it should already be handled during card confirmSetup above
* But for some cards we face the situation where we need to confirm
* 3d secure again for the transaction itself
*
* Most other cards do not require further 3d secure confirmation after
* the initial card setup
*/
if (result.client_secret) {
const stripeClient = getStripeClient();
const { error } = await stripeClient.confirmPayment({
clientSecret: result.client_secret,
redirect: "if_required",
});
if (error) throw error;
}
} catch (e) {
log.error(e);
// analytics
this.paymentError = (e as Error).message;
}
i set the payment error state upon failure
is there a way to add card and subscribe in one flow?
instead of doing what im doing here where i set up card and then save the card as default method?
You can create a Subscription like this for example:
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{
price: priceId,
}],
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.confirmation_secret'],
});
If you pass the subscription.latest_invoice.confirmation_secret.client_secret to the frontend and use that in the Payment Element, it will attach the card to the customer.
I wonder if it has something to do with redirecting?
redirect due to 3d secure?
how can i test that out?
how can I test out the redirect url flow for 3d secure
i have a feeling it may be the cause behind duplicate subscriptions being created for customers
we have test cards that trigger 3DS flow: https://docs.stripe.com/testing#regulatory-cards