Exclusive Discount Deal
Upto 50% OFF
Offer ends in:
20 DAYS
|
22 HOURS
|
12 MINS
|
14 SECS
Home / Blog / Usage-Based Metered Billing with Stripe Connect & Webhooks
SaaS & Platforms • Oct 11, 2026

Usage-Based Metered Billing with Stripe Connect & Webhooks

Architect scalable event ingestion and idempotent webhook processing for usage-based metered billing tiers using Stripe Connect, Redis, and PostgreSQL.

UPTO 50% OFF
Trending:
BrickTry

Requirement Scope

AI is analyzing your requirement...

Generating custom modules, implementation options, and dynamic clarification questions.

Add Custom Requirement or Module

Add your own specific features, integrations, or components. AI will incorporate them to dynamically generate the next relevant options.

1. Progressive Clarifications

Click to expand & answer

2. Scope Modules & Features (/ Selected)

Click row to expand details · Customize options
✓
✕
Completeness:

Usage-based billing models—charging per API call, compute-second, or gigabyte processed—introduce significant technical complexity into multi-tenant SaaS platforms and digital marketplaces. When integrated with Stripe Connect to charge on behalf of connected accounts, software architects face four critical challenges:

  1. Hot-Path Latency Overhead: Synchronously calling external payment APIs during application request processing degrades user experience and introduces single-point-of-failure vulnerabilities.
  2. Event Delivery Guarantees: Billing systems require strict exactly-once event semantics to prevent underbilling (data loss) or overbilling (duplicate counting).
  3. Multi-Tenant Routing: Charging usage against connected accounts requires scoped API credentials and precise Stripe-Account context handling.
  4. Asynchronous State Reconciliation: Processing out-of-order webhooks requires idempotent state machines to handle payment failures, retry policies, and account provisioning locks.

This blueprint details a resilient architecture for ingestion, batch aggregation, and webhook reconciliation using Redis, PostgreSQL, and Node.js/TypeScript.


System Architecture: Event Ingestion to Reconciliation

To handle thousands of concurrent usage events per second without exceeding Stripe API rate limits (100 requests/second in live mode), you must separate ingestion from reporting.

[ Client Request ]
       │
       ▼
[ Edge Middleware ] ──── (1. Write Counter) ───► [ Redis Buffer (Sliding Window) ]
       │                                                      │
       ▼                                                      │ (2. Worker Poll)
[ Application Logic ]                                         ▼
                                                [ Aggregation Pipeline Worker ]
                                                              │
                                                              │ (3. Batch Meter Events)
                                                              ▼
                                                [ Stripe Billing Meter API ]
                                                              │
                                                              │ (4. Async Webhooks)
                                                              ▼
[ PostgreSQL Database ] ◄─── (5. Idempotent Write) ─── [ Webhook Ingestion Engine ]

Architectural Layering & Storage Matrix

System Layer Primary Technology Performance Profile Consistency Level Primary Purpose
Ingestion Buffer Redis (In-Memory) $< 1\text{ms}$ latency Eventual (In-Memory) Aggregates high-frequency counters in sliding temporal windows.
Durable Ledger PostgreSQL (Append-Only) $5\text{--}10\text{ms}$ latency Strict ACID Permanent audit trail of every usage event tagged with UUIDs.
Meter Aggregator Node.js / BullMQ Scheduled Batches At-Least-Once Delivery Groups metrics and executes payloads against Stripe Meters API.
Webhook Handler Express / PostgreSQL Transactional Strict Serializability Processes lifecycle events (invoice.paid) using row locks.

High-Throughput Usage Event Ingestion Engine

Rather than calling Stripe on every HTTP request, increment an atomic counter in Redis using a compound key containing the tenant, feature, and time bucket. A background worker periodically flushes these aggregated counters to Stripe’s Billing Meters API (/v1/billing/meter_events).

TypeScript: Distributed Batch Meter Dispatcher

import Stripe from 'stripe';
import Redis from 'ioredis';
import crypto from 'crypto';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
});
const redis = new Redis(process.env.REDIS_URL!);

interface UsagePayload {
  tenantId: string;
  stripeAccountId?: string; // Present for Stripe Connect
  stripeCustomerId: string;
  eventName: string;
  value: number;
}

export class MeteredBillingService {
  /**
   * Ingest usage event into Redis hot buffer using atomic increments
   */
  async recordUsage(payload: UsagePayload): Promise<void> {
    const windowBucket = Math.floor(Date.now() / 60000); // 1-minute bucket
    const key = `usage:${payload.tenantId}:${payload.eventName}:${windowBucket}`;

    const pipeline = redis.pipeline();
    pipeline.hincrby(key, 'value', payload.value);
    pipeline.hset(key, 'stripeCustomerId', payload.stripeCustomerId);
    if (payload.stripeAccountId) {
      pipeline.hset(key, 'stripeAccountId', payload.stripeAccountId);
    }
    pipeline.expire(key, 86400); // 24-hour retention
    await pipeline.exec();
  }

  /**
   * Flush aggregated metrics to Stripe Billing Meters API
   */
  async flushMeterEvents(keys: string[]): Promise<void> {
    for (const key of keys) {
      const data = await redis.hgetall(key);
      if (!data || !data.value) continue;

      const [, tenantId, eventName, timestampBucket] = key.split(':');
      const timestamp = parseInt(timestampBucket, 10) * 60;

      // Deterministic idempotency key per minute bucket
      const idempotencyKey = crypto
        .createHash('sha256')
        .update(`${tenantId}:${eventName}:${timestampBucket}`)
        .digest('hex');

      const stripeOptions: Stripe.RequestOptions = {};
      if (data.stripeAccountId) {
        // Scope meter event to Connected Account if applicable
        stripeOptions.stripeAccount = data.stripeAccountId;
      }

      try {
        await stripe.billing.meterEvents.create(
          {
            event_name: eventName,
            payload: {
              value: data.value,
              stripe_customer_id: data.stripeCustomerId,
            },
            timestamp: timestamp,
            identifier: idempotencyKey, // Prevents duplicate charges
          },
          stripeOptions
        );

        // Delete processed key to prevent double processing
        await redis.del(key);
      } catch (error) {
        console.error(`Failed to flush billing key ${key}:`, error);
        // Retain key in Redis for subsequent worker retry cycles
      }
    }
  }
}

Idempotent Webhook Processing Architecture

Stripe guarantees at-least-once webhook delivery. Network instability or worker timeouts can cause duplicate event deliveries. Webhook ingestion handlers must implement strictly idempotent transactions using PostgreSQL database locks.

                  [ Incoming Stripe Webhook ]
                              │
                              ▼
           [ Parse & Verify Signature (HMAC-SHA256) ]
                              │
                              ▼
            [ Begin PostgreSQL Serial Transaction ]
                              │
                              ▼
    [ SELECT id FROM stripe_webhooks WHERE id = ? FOR UPDATE ]
                              │
               ┌──────────────┴──────────────┐
               │                             │
    (Already Processed?)              (New Event?)
               │                             │
               ▼                             ▼
       [ Rollback & Return ]         [ Record Event ID ]
          [ HTTP 200 OK ]                    │
                                             ▼
                                   [ Update Subscription State ]
                                             │
                                             ▼
                                     [ Commit Transaction ]

TypeScript: Atomic Webhook Processing Handler

import { Request, Response } from 'express';
import Stripe from 'stripe';
import { Pool } from 'pg';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-11-20.acacia' });
const pgPool = new Pool({ connectionString: process.env.DATABASE_URL });

export async function handleStripeWebhook(req: Request, res: Response): Promise<void> {
  const sig = req.headers['stripe-signature'];
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig!, webhookSecret);
  } catch (err: any) {
    res.status(400).send(`Webhook Signature Error: ${err.message}`);
    return;
  }

  const client = await pgPool.connect();

  try {
    await client.query('BEGIN');

    // Acquire lock and insert webhook event ID atomically
    const insertLogQuery = `
      INSERT INTO processed_webhooks (event_id, event_type, created_at)
      VALUES ($1, $2, NOW())
      ON CONFLICT (event_id) DO NOTHING
      RETURNING id;
    `;
    const lockResult = await client.query(insertLogQuery, [event.id, event.type]);

    if (lockResult.rowCount === 0) {
      // Event has already been processed by a parallel worker
      await client.query('ROLLBACK');
      res.status(200).json({ status: 'ignored', reason: 'duplicate_event' });
      return;
    }

    // Process specific lifecycle events
    switch (event.type) {
      case 'invoice.payment_succeeded': {
        const invoice = event.data.object as Stripe.Invoice;
        await client.query(
          `UPDATE tenant_subscriptions
           SET status = 'active',
               last_paid_at = TO_TIMESTAMP($1)
           WHERE stripe_customer_id = $2`,
          [invoice.status_transitions.paid_at, invoice.customer]
        );
        break;
      }

      case 'invoice.payment_failed': {
        const invoice = event.data.object as Stripe.Invoice;
        await client.query(
          `UPDATE tenant_subscriptions
           SET status = 'past_due'
           WHERE stripe_customer_id = $1`,
          [invoice.customer]
        );
        // Provisioning logic: Restrict high-cost API usage
        break;
      }

      default:
        // Unhandled events are acknowledged without failing
        break;
    }

    await client.query('COMMIT');
    res.status(200).json({ received: true });
  } catch (dbError) {
    await client.query('ROLLBACK');
    console.error('Webhook execution transaction failed:', dbError);
    res.status(500).send('Internal Server Error');
  } finally {
    client.release();
  }
}

Production Security & Resilience Audit Checklist

Deploying billing pipelines requires robust safeguards against edge cases that can compromise financial accuracy:

  1. Replay Attack Safeguards: Always verify signatures using stripe.webhooks.constructEvent() combined with strict tolerance checks (Stripe defaults to 300 seconds).
  2. Clock Drift Tolerance: Ensure all application hosts run NTP synchronization daemon (chrony) to keep timestamps within milliseconds of Stripe servers.
  3. Stripe API Rate Limit Backoff: Implement exponential backoff algorithms with randomized jitter when dispatching bulk meter events to absorb HTTP 429 responses.
  4. Out-of-Order Webhook Protection: Compare event creation timestamps (event.created) in local state rather than trusting arrival sequence.

How BrickTry Accelerates & Powers This

Architecting, testing, and scaling usage-based billing infrastructure demands zero-tolerance execution. BrickTry provides the tooling, infrastructure, and engineering expertise required to take metered billing architectures from blueprint to production.

[ BrickTry Unified Importer ] ──► [ Local / Envato / GitHub Repo ]
                                              │
                                              ▼
[ Interactive Lab (/lab) ]    ──► [ Zero-Setup In-Browser Container Engine ]
                                              │
                                              ▼
[ AI Dev Pairing ]            ──► [ Scaffolds Models, Queue Workers & Webhooks ]
                                              │
                                              ▼
[ Senior Engineering Pods ]   ──► [ Architecture Review & Production Verification ]

1. In-Browser Prototyping Engine (/lab)

Test meter aggregation queues and simulate Stripe webhook deliveries inside the BrickTry Lab (/lab)—an in-browser Node/Vite virtual container runtime. You can spin up isolated PostgreSQL and Redis instances, execute webhooks via mock triggers, and analyze real-time execution profiles without setting up local database dependencies.

2. Autonomous Scaffolding & AST Code Auditing

BrickTry’s AI Dev Pairing analyzes your database schemas and automatically generates:

  • Strictly typed TypeScript interfaces mapping Stripe Connect webhook payloads to internal entities.
  • Optimized Abstract Syntax Tree (AST) migrations ensuring PostgreSQL unique constraints, foreign keys, and idempotency indices are applied correctly.
  • Redis buffer pipelines configured for high throughput out of the box.

3. Senior Technical Pod Verification

AI-generated boilerplate is reviewed and enhanced by BrickTry Senior Engineering Pods. Principal Architects manually audit your billing workflows to ensure:

  • Zero race conditions exist in multi-tenant payout distribution routes.
  • Webhook ingestion paths implement strict ACID database transaction boundaries.
  • Metering scripts adhere to PCI-DSS compliance boundaries and Stripe API rate limitations.

4. Direct Repo Integration & 100% Code Ownership

Whether starting from a clean repository or refactoring existing CodeCanyon platforms using the Unified Importer, BrickTry operates directly on your GitHub repositories and infrastructure configs. You retain 100% full source code ownership with no proprietary runtimes or vendor lock-in.


Conclusion

Usage-based metered billing with Stripe Connect requires decoupling event generation from payment execution. By buffering metrics using Redis counters, flushing aggregated batches with deterministic idempotency keys, and consuming webhooks using transactional database locks, you create a robust billing pipeline capable of handling enterprise scale. Launch your pipeline with BrickTry to accelerate development, verify architectural integrity, and deploy production-ready billing systems.

Build, Test, and Scale This on BrickTry

BrickTry pairs you with autonomous AI scaffolding supervised by dedicated senior full-stack software engineers in an interactive in-browser development sandbox. Test, build, and deploy production-grade software with 100% source code ownership and zero vendor lock-in.

Launch Interactive Requirement Builder →

❤️

Support BrickTry Platform & Engineering Development

Help us build, maintain, and advance our AI engineering platform. Every donation fuels open-source tooling, infrastructure, and continuous improvements.

$
Donor Details
Promote Your Brand / Link Wall

UPI / Credit & Debit Cards / Netbanking
Razorpay
Secure 256-bit encrypted checkout
View Leaderboard & Wall

Hey!

Welcome, Let's chat —
start a new conversation
below.

Recent conversations
See all

We’re online to assist you with your project...

Abhishek A Agrawal • Just now

Start a conversation

Quick contact setup

Please share your details below so our team can reach you.

Worldwide supported

🔒 Your info is only used to connect with our support team.

Abhishek A Agrawal

Online & Ready to Assist