stubkit

Stripe Subscriptions

One subscription webhook for Stripe, Apple, and Google.

A managed Stripe webhook endpoint with signature verification, idempotent retries, and customer portal support — merged into a single lifecycle stream alongside your App Store and Play Store events.

Running Stripe + App Store + Play Store separately is painful

  • Each platform has its own event model, retry behaviour, and idempotency strategy. Gluing them together is a recurring source of subtle revenue bugs.
  • Stripe subscription events alone span 30+ types — customer.subscription.updated, invoice.payment_failed, charge.dispute.created, checkout.session.completed, plus Customer Portal-initiated changes.
  • Signature verification must be correct on every request, or you silently ship a spoofable billing endpoint.
  • When Stripe retries (3-day window) or you rebuild your consumer, you need bulletproof idempotency to avoid double-grants.

What Stubkit handles for you

  • One webhook URL per app for Stripe, with signature verification and event.id-keyed idempotency.
  • Full coverage of subscriptions, invoices, refunds, chargebacks, and Customer Portal state changes.
  • Normalised outbound events that merge with your Apple + Google lifecycle stream — one handler, one event shape.
  • Delivery log with automatic retries for your own backend webhook, inspectable from the dashboard.

See it in action

Ship the integration in minutes with the Stubkit SDK — the receipt flow, retries, and idempotency are handled server-side.

// Paste the Stubkit webhook URL into Stripe → Developers → Webhooks,
// upload your webhook signing secret, and the Stripe subscription
// lifecycle merges into your unified Stubkit event stream.

import { Stubkit } from '@stubkit/js';

const stubkit = new Stubkit({ apiKey: process.env.STUBKIT_SECRET_KEY });

// One webhook for Apple, Google, AND Stripe — same event shape.
app.post('/webhooks/stubkit', async (req, res) => {
  const event = stubkit.verifyWebhook(req.body, req.headers['stubkit-signature']);

  if (event.type === 'subscription.activated') {
    await grantEntitlement(event.data.user_id, event.data.entitlement);
  }
  if (event.type === 'subscription.refunded') {
    await revokeEntitlement(event.data.user_id, event.data.entitlement);
  }
  res.sendStatus(200);
});

Common questions

Why not just handle Stripe webhooks directly?+

You can — but most apps end up writing the same scaffolding: signature verification, idempotency keyed on event.id, a durable queue for retries, a mapping from Stripe event names to entitlement state, and separate handling for customer portal events versus checkout events. Stubkit ships all of this, and merges it with your Apple and Google streams into one API.

Does Stubkit support Stripe Checkout and Customer Portal?+

Yes. Stubkit listens to the full set of Stripe subscription events (customer.subscription.*, invoice.*, charge.refunded, checkout.session.completed) plus Portal-originated changes like plan swaps and cancellations. The outbound normalised event reflects the effective entitlement state regardless of which surface triggered it.

How is signature verification handled?+

On the inbound path, Stubkit verifies the Stripe-Signature header against your webhook signing secret before accepting the event. On the outbound path, every webhook Stubkit sends you carries a stubkit-signature header that you verify with a single SDK call.

What happens during Stripe retries?+

Stripe retries failed deliveries for up to 3 days. Stubkit's ingestion is idempotent on event.id, so replays are silently de-duplicated. If Stubkit's outbound webhook to your backend fails, Stubkit retries with exponential backoff for up to 24 hours — you can inspect the full delivery log in the admin dashboard.

Can I run Stripe alongside Apple + Google in one app?+

That's the core use case. A single Stubkit app can have Apple, Google Play, and Stripe subscriptions all pointing at the same entitlement ledger — so a user who subscribed on your website via Stripe is treated identically to a user who subscribed through the App Store, and the paywall / entitlement API serves both transparently.

What about refunds and chargebacks?+

Stubkit fires a subscription.refunded event the moment Stripe reports a charge.refunded, charge.dispute.created, or a credit note that zeroes out an invoice. Your entitlement is revoked automatically, with the reason exposed in the event payload for analytics.

Ship the integration, not the infrastructure.

Free forever on the starter tier. Full SDK support for iOS, Android, web, and server environments.

Start free

Already integrated? See pricing