The four ways a Stripe integration goes wrong in production

Most Stripe failures are not Stripe bugs. They are payment systems built without understanding what happens when things go half-done: webhooks fire twice, subscriptions charge wrong prices, disputes land in a void, and tax bills show up in January.

Stripe integrations are deceptively simple to get working. The test mode passes. Money flows. But production catches you in ways that do not surface until you have thousands of customers and your calendar is booked for three months.

We have shipped Stripe integrations for thirty startups. The failures are always the same four. This post defends the thesis that you can avoid all of them with one principle: assume every webhook fires twice, subscriptions change mid-month, platforms have fraud, and your tax jurisdiction is ambiguous.

Failure mode 1: Webhooks without idempotency

Stripe webhooks are reliable. They retry for 3 days. They are also not atomic. A webhook can arrive twice at 145 milliseconds apart. Your `payment.success` handler runs twice before the database saves the first result.

The symptom: a customer deposits $100. Your webhook processes it. The network hiccup. Stripe retries. The webhook processes it again. Now your database has two $100 deposits. You do not notice until the customer reports a duplicate charge.

The fix is idempotency. Every Stripe event has an `id` field. That `id` never changes. Store it. Check it. Only process each event once.

// Node.js + PostgreSQL example
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/webhooks/stripe', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);

  // Check if we've processed this event before
  const existing = await db.query(
    'SELECT id FROM stripe_events WHERE event_id = $1',
    [event.id]
  );

  if (existing.rows.length > 0) {
    return res.json({received: true}); // Already processed
  }

  // Process the event
  if (event.type === 'charge.succeeded') {
    await db.query(
      'INSERT INTO stripe_events (event_id, event_type) VALUES ($1, $2)',
      [event.id, event.type]
    );
    // Now handle the charge
    await processChargeSucceeded(event.data.object);
  }

  res.json({received: true});
});
      

Stripe gives you 30 days to acknowledge a webhook. In that window, any webhook can be replayed. Use the Stripe API to fetch the full event object rather than trusting the webhook payload. The payload can be tampered with. The API fetch is the source of truth.

Most teams do not do this. The cost is low. The benefit is infinite. One duplicate charge detection saved a marketplace $8,500 in January 2024 when a payment processor hiccup sent webhooks in bursts.

Failure mode 2: Subscriptions without proration math

Subscriptions seem simple until mid-month changes. A customer upgrades from $30 per month to $50 per month on day 15. How much should they pay. $10 for the remaining 15 days. Or nothing, because it is their problem. Or they get credited $5 for overpaying if they already paid.

Stripe does not guess. You tell Stripe what to do with the `proration_behavior` flag. Three options exist:

The cost difference is significant. A customer who upgrades partway through a month might see a $5 credit or a $20 charge, depending on your choice. Neither is obvious from the code.

Real example from Q2 2024: A SaaS client we shipped had 800 active subscriptions. They added a new tier and allowed upgrades without specifying `proration_behavior`. Stripe defaulted to `create_prorations`. Over a month, the system issued $12k in credits and $14k in charges to the same cohort of users. The finance team thought something was broken. It was not. It was proration working correctly on a decision they did not make consciously.

You need one more thing: understanding what a prorated invoice item looks like. When you query Stripe for an invoice, prorations show up as separate line items with negative amounts. Your billing dashboard needs to display these. Most teams hide them. Customers see their bill and do not understand why their credit card was charged half of what they expected.

Failure mode 3: Connect platforms without dispute routing

If you run a marketplace, you use Stripe Connect. Sellers' cards are connected to your account. Stripe holds the money. You decide how to split it. Revenue flows from buyer to your account to seller. You set the fee percentage.

Then a seller gets charged back. The customer claims they never received the goods. The chargeback comes to your Stripe account first. Now you own the relationship with both the seller and the customer. And you own the fraud risk.

The mistake: not implementing a dispute webhook handler. You receive the `charge.dispute.created` event. You do nothing. Sixty days later, you lose $5k. Stripe covers nothing. You never told the seller to defend themselves.

The fix is a three-step process.

  1. Listen to `charge.dispute.created` events. Extract the seller's ID from the charge metadata.
  2. Notify the seller immediately. Give them 7 days to respond. If they have evidence (tracking number, signed receipt), they can submit it to Stripe via the API.
  3. Log the dispute in your database. Mark the transaction as contested. Do not pay the seller until the dispute is resolved. Stripe takes 45-75 days to resolve each dispute.

Stripe Radar can also help. It flags high-risk transactions before they complete. Set it to `block` mode for your highest-risk verticals. It is slower than pure fraud rules, but it catches issues your rules miss.

Failure mode 4: Tax compliance left to the last week

You launch in the US. No one thinks about tax. Two years later you expand to Europe. VAT MOSS applies. You now need to file 27 tax returns. It is March. Taxes are due April 20. You have 30 days.

The mistake is not deciding early what you will do. Stripe Tax exists. It costs $0.005 per calculation. For a $1M SaaS company, that is under $200 per month.

ApproachCost (tax logic)Time to buildWhen to choose it
Stripe Tax$0.005 per calc2-3 daysMost teams, all stages
Custom + Avalara$100 per mo base6-8 weeksHigh complexity, 50+ nexus
Build custom logic$0 direct12-16 weeksOnly if you have very specific rules

Stripe Tax handles US sales tax, EU VAT, UK VAT, and India GST. It integrates with Stripe Billing. You call one API. Stripe calculates, collects, and files for you. In 2026, you do not build this yourself.

The India GST case is interesting. India allows a 5 percent margin scheme for certain verticals. You need to file returns showing what you collected and what tax you owe. Stripe Tax does not do this. You need a separate integration with Razorpay or a local CA. It is doable. But it is a distinct project.

The pre-launch checklist we run

Before any Stripe integration goes live, we verify all of these in order.

  1. Webhook idempotency key stored before any business logic runs.
  2. Stripe Signature header validated. We verify the signature is not older than 5 minutes.
  3. All webhook types tested in production sandbox: charge, refund, dispute, invoice lifecycle, subscription events.
  4. Proration behavior explicitly chosen for all subscription plans. Documented in code comments.
  5. Dunning logic implemented: failed charges retry with exponential backoff. Stop after 5 failures, notify the user.
  6. Subscription cancellation: when a customer cancels, we check if they have a balance credit. If so, we refund it. If not, we do nothing.
  7. Prorated invoice items displayed correctly in the customer dashboard. No surprises.
  8. For Connect platforms: dispute webhook handler deployed. Seller notification sent within 1 hour of dispute creation.
  9. Tax compliance: if selling to multiple regions, Stripe Tax or custom logic is live and tested.
  10. PCI scope validation: verify we are not storing raw card data anywhere. No card numbers in logs. No PAN in error messages.
  11. Stripe API version pinned in code. We do not auto-upgrade to the latest API.
  12. Webhook replay tested: manually trigger a test webhook in Stripe Dashboard. Verify idempotency key prevents duplicate processing.

Where this lives at Empyreal

Stripe integrations are part of our core. We ship Stripe integrations for SaaS startups, fintech platforms, and marketplaces. Every team we work with gets our pre-launch checklist. If you are building a payment system and want to avoid the four failure modes, we can help. See our Stripe integration service page. We also have deep fintech expertise. Read about fintech architecture or our SaaS architecture services. And if you are curious about payment terms and concepts, check our architecture glossary.

Frequently asked questions

How long does a Stripe integration typically take?

Simple one-time payments take 3-5 days. Subscriptions with proration add 1-2 weeks. Multi-tenant platforms with Connect add 4-6 weeks for dispute handling and settlement logic. Tax compliance adds 2-3 weeks depending on your target jurisdictions.

Do you handle PCI compliance for us?

Stripe handles PCI compliance on your behalf when you use their hosted payment forms or Payment Intents. You never see raw card data. Your backend only handles tokens and IDs, which are safe to log and store.

What is the cost difference between one-time and subscription integrations?

Stripe charges the same percentage (2.2% + 30 cents in the US). The difference is engineering cost. Subscriptions require webhook handlers, idempotency keys, proration logic, and customer lifecycle events. Figure 5-10x more development time.

Should we use Stripe Tax or build our own?

Use Stripe Tax if you ship to multiple countries. Building tax logic yourself costs $15-40k and takes 8-12 weeks. Stripe Tax costs $0.005 per calculation (under $100 per mo for most SaaS). The only reason to build custom is if you have unusual nexus rules.

Need help with a Stripe integration?

We build payment systems that do not fail in production. From webhook idempotency to tax compliance, we handle all four failure modes so you do not have to.

Get in touch

About the author

Mohit Ramani is the Founder & Lead Architect at Empyreal Infotech. Over 15+ years, he has shipped 200+ production systems for startups and agencies—from payment platforms and fintech backends to AI integrations and platform migrations. He leads architecture reviews on every Empyreal project and writes about production-grade engineering at scale. LinkedIn