Adding Payments to Your Product with Stripe

Taking a card payment is a week of work. Running a system that still agrees with Stripe after six months of refunds, disputes, failed renewals and partial captures is the actual project, and it is the part estimates miss.

Here is what is really involved.

What you are actually building

ScenarioRealistic effortWhat makes it that
One off card payment1 weekCheckout, webhook, receipt
Subscriptions3 to 4 weeksPlan changes, proration, dunning, cancellation
Marketplace payouts6 to 10 weeksConnect onboarding, identity checks, split logic, payout timing
Funds held until delivery8 to 12 weeksAll of the above plus a ledger and a dispute process

The pattern is that complexity scales with how long money sits in an uncertain state, not with how many payment methods you accept.

The webhook is the real integration

The single most important thing to understand: the browser redirect after checkout is not proof of payment. A customer can close the tab, lose signal or hit back. The webhook is the source of truth and it arrives independently.

Three rules follow, and skipping any of them produces bugs that are maddening to diagnose because they only appear under conditions you cannot easily reproduce.

Verify the signature. An unverified webhook endpoint is an open API that grants whatever the payload claims. Use the library's verification with your signing secret, and reject anything that fails.

Be idempotent. Stripe retries, and will deliver the same event more than once. Store processed event ids and make a repeat a no op. Without this, a retry grants the subscription twice or sends two receipts.

Accept events out of order. You will sometimes receive a charge succeeded before the record it belongs to exists locally. Handle the event against the Stripe object ids rather than assuming your own state is already correct.

Do not mirror Stripe's data model badly

Two failure modes, and both are common.

The first is treating your database as authoritative for payment state. It is not. Stripe knows whether a charge succeeded; your row is a cache of that fact.

The second is storing nothing locally and calling the API for every read. That makes your product slow, fragile when Stripe is degraded, and your own reporting impossible.

The middle path: store the Stripe object id, the status and the few fields you display or query, and refresh them from webhooks. Treat your copy as a read model that Stripe updates.

Subscriptions are where the quiet complexity is

A subscription looks simple until you write down what happens in each of these cases, and all of them will happen:

  • A customer upgrades mid cycle. Do you prorate, and what do they see on the invoice?
  • A card expires on renewal. How many times do you retry, over how many days, and what does the customer see while that is happening?
  • Someone cancels on day 3 of 30. Immediate loss of access, or at period end?
  • A payment fails after access was granted. At what point do you revoke, and how do you tell them first?
  • Someone disputes a charge from four months ago. Who is told, and does access change?

Each is a product decision before it is an engineering one, and leaving them to be decided implicitly during implementation is how subscriptions take three weeks longer than planned.

Marketplaces: the identity work is the long pole

If you pay third parties, Stripe Connect handles the mechanics, but the surrounding requirements are the schedule risk. Sellers must be verified before they can be paid, which means identity documents, business details and tax information, with the exact requirements varying by country.

Expect a meaningful fraction of sellers to stall partway through onboarding, so build for the partially verified state from the start rather than assuming a clean binary. You also need to decide when funds move: at purchase, at delivery, or on a schedule. That choice determines whether you need a ledger of your own, which is the difference between six weeks and twelve.

Test the paths nobody tests

Everyone tests a successful payment. The bugs live elsewhere, and all of these are reproducible with Stripe's test cards and the CLI:

  • Card declined, then retried successfully with a different card.
  • Customer closes the tab after paying. Does the webhook still grant access?
  • Duplicate webhook delivery. Does anything happen twice?
  • Refund, full and partial. Does access and reporting follow?
  • Dispute opened and then lost.
  • Renewal failing three times and then succeeding.

Run these in CI against test mode. They are the paths that cost real money when they are wrong, which is the same argument we make about test coverage in our guide to choosing an agency: cover what touches money, not everything.

Frequently asked questions

How long does it take to integrate Stripe?

About a week for a one off card payment, three to four weeks for subscriptions, and six to twelve weeks for a marketplace with payouts. The variable is not the payment itself but how long money sits in an uncertain state, because that is what creates refunds, disputes, dunning and reconciliation work.

Do I need to handle Stripe webhooks?

Yes. The redirect back to your site after checkout is not proof of payment, because the customer can close the tab or lose connectivity. The webhook is the source of truth. Verify its signature, make processing idempotent so retries are harmless, and tolerate events arriving out of order.

Should I store payment data in my own database?

Store the Stripe object id, the status and the few fields you display or query, and keep them updated from webhooks. Never treat your own row as authoritative for whether a payment succeeded, and never call the API on every read. Your copy is a read model that Stripe updates.

What is dunning and do I need it?

Dunning is the retry process when a renewal payment fails, usually an expired card. You need it for any subscription product: without it, ordinary card expiry silently churns customers who fully intended to keep paying. Decide how many retries, over how many days, and what the customer sees meanwhile.

Is Stripe Connect hard to implement?

The payment mechanics are straightforward. The onboarding is the schedule risk: sellers must pass identity and business verification before they can receive money, requirements vary by country, and a meaningful fraction stall partway. Build for the partially verified state rather than assuming verified or not.

Am I PCI compliant if I use Stripe?

Largely, provided card details never touch your servers. Use Stripe's hosted checkout or its client side elements so the card data goes directly to Stripe. The moment you accept raw card numbers on your own backend, the compliance burden becomes yours and it is substantial.

Where to start

Write down what happens in each of the five subscription cases above before any code. If your team cannot agree on the answers in a meeting, the integration was never going to take one week.

If money movement is central to what you are building, tell us the shape of it and we will tell you which of those four rows you are actually in.

Venturial

Your product, built end to end.

Mobile apps, web platforms, payment systems and AI agents. One team, from the first call to production and the version after it.

Talk to engineeringBack to the blog