Back to Blog
SaaS

Stripe Integration: Everything Their Docs Don't Tell You

Zyptr Admin
11 March 2024
9 min read

Stripe's Docs Are Good. Production Is Different.

We love Stripe. We've integrated it into eight production SaaS products and it's genuinely the best payment infrastructure available. But there's a gap between "follow the quickstart guide" and "handle every edge case in production." Here are the things we've learned that Stripe's docs don't adequately cover.

Webhook Reliability Is Not 100%

Stripe's webhooks are reliable, but "reliable" doesn't mean "guaranteed." We've experienced webhook delivery delays of up to 4 hours during Stripe's incident on March 14, 2024. If your application assumes webhooks arrive within seconds of an event, you'll have inconsistent state during incidents.

Our solution: implement a reconciliation job that runs every 15 minutes. It fetches recent events from Stripe's API (using the Events endpoint with created[gte] filter) and compares them to our processed events log. Any events we received via webhook are skipped; any missing events are processed. This catches both missed webhooks and out-of-order delivery. The job adds about $0.02/day in API costs for a mid-traffic SaaS. Cheap insurance.

Also: always return a 200 response to webhook endpoints quickly, before processing. We've seen teams do database operations, email sending, and API calls inside the webhook handler, which causes Stripe to timeout and retry, leading to duplicate processing. Receive the webhook, acknowledge it, and process it asynchronously via a job queue.

Idempotency Keys: Use Them Everywhere

Any Stripe API call that creates or modifies a resource should include an idempotency key. Network failures happen — your request reaches Stripe but the response doesn't make it back. Without an idempotency key, retrying the request creates a duplicate charge. With an idempotency key, Stripe returns the original response. We generate idempotency keys by hashing the operation parameters: SHA256(user_id + action + amount + timestamp_rounded_to_minute). This ensures the same logical operation always uses the same key.

Subscription Lifecycle Is More Complex Than You Think

The happy path is simple: customer subscribes, gets charged monthly, everyone's happy. The real world: customers upgrade mid-cycle (proration!), downgrade mid-cycle (credits!), dispute charges (chargeback handling!), have payment failures (dunning!), pause subscriptions (not natively supported!), and need custom billing dates.

Proration alone has burned us multiple times. Stripe's default proration behavior creates a prorated invoice item on the next invoice. But if the customer's next invoice isn't due for 29 days, they've been upgraded for 29 days before being charged. For SaaS products where the upgrade unlocks expensive features (like AI-powered analysis that costs you per-use), this is a problem. We now set proration_behavior: 'always_invoice' for upgrades, which creates and charges an invoice immediately for the prorated amount.

Testing Is Harder Than It Should Be

Stripe's test mode is useful but limited. Test clocks (for simulating the passage of time) are essential for testing subscription renewals, trial expirations, and dunning sequences. But test clocks have limits: you can only advance time forward (not backward), each test clock is independent (no shared time context), and certain events don't fire on test clocks the way they do in production.

We maintain a dedicated Stripe test account that we periodically reset. Our integration tests run against Stripe's API directly (not mocked) using test mode API keys. This catches API version changes, parameter validation changes, and webhook schema changes that mock-based tests would miss. Yes, it's slower than mocked tests (each test takes 2-5 seconds for the Stripe round-trip), but the confidence is worth it.

The India-Specific Gotchas

For Indian merchants using Stripe: UPI recurring payments are handled differently than card subscriptions. The mandate creation flow requires an additional authorization step from the customer's banking app, and mandate failures are more common than card failures (about 8% vs 3% in our experience). We implement a fallback flow: if UPI mandate creation fails, prompt the user to set up a card on file instead.

Also, INR settlements have a T+2 business day settlement cycle, not the T+1 that Stripe offers in the US. Plan your cash flow accordingly, especially if you're paying vendors or API providers in USD — the settlement delay plus currency conversion timing can create cash flow gaps.

stripepaymentssaasintegration
Let's Work Together

Have a Project in Mind?
Great?

Let's talk about building your next product.