Build usage-based billing systems for SaaS platforms in Webflow using Stripe

Connect Webflow to Stripe's metered billing APIs through serverless middleware for usage-based SaaS pricing.

Build usage-based billing systems for SaaS platforms in Webflow using Stripe

Implement consumption-based billing in Webflow using Stripe meters, webhooks, and serverless functions.

Usage-based billing lets SaaS platforms charge customers for actual consumption rather than flat subscription fees. This guide explains the architectural patterns and implementation concepts for connecting Webflow frontends to Stripe's metered billing infrastructure through middleware.

Webflow operates as a client-side-only platform with no server-side code execution capability. Stripe requires server-side operations for Checkout Sessions, Customer Portal sessions, and webhook processing. This constraint makes external middleware (such as Vercel Functions, Cloudflare Workers, or AWS Lambda) mandatory for any production implementation.

Prerequisites

Before implementing this integration, ensure you have:

  • A Stripe account with API keys configured
  • A Webflow site with access to custom code sections
  • A middleware hosting platform (Vercel, Cloudflare Workers, AWS Lambda, or similar)
  • Basic understanding of webhooks and REST APIs 

You'll need these credentials:

  • Stripe publishable key (pk_test_* or pk_live_*) for frontend initialization
  • Stripe secret key (sk_test_* or sk_live_*) for server-side operations
  • Stripe webhook signing secret (whsec_*) for signature verification

Architecture overview

The integration requires a three-tier architecture because Webflow's client-side-only environment cannot execute server-side code or store secret API keys securely.

At a high level, you'll:

  • Configure Webflow to load Stripe.js and handle UI events
  • Deploy middleware to create sessions, process webhooks, and store API keys
  • Set up Stripe products with metered pricing and billing meters

Data flow summary:

  1. Webflow handles UI presentation and initializes Stripe.js with publishable keys
  2. Middleware receives requests from Webflow, authenticates with Stripe using secret keys, and processes webhook events
  3. Stripe manages payment processing, hosted checkout pages, and billing meter aggregation
  4. External storage tracks usage events and customer subscription state

Stripe metered billing concepts

Stripe provides two complementary APIs for reporting usage data, each suited to different volume requirements: the Usage Records API for subscription-item-specific usage tracking, and the higher-throughput Billing Meter API for high-volume usage event ingestion.

At a high level, you'll:

  • Create a billing meter with an event_name and default_aggregation configuration
  • Send meter events to POST /v1/billing/meter_events with customer ID and usage value
  • Configure metered pricing on your subscription products

The meter event payload structure:

{
  "event_name": "api_requests",
  "payload": {
    "value": "150",
    "stripe_customer_id": "cus_NciAYcXfLnqBoz"
  },
  "timestamp": 1680210639
}

Stripe's rate limits constrain API request frequency. Pre-aggregate usage events in your middleware before submission to stay within limits.

Webflow frontend implementation

Webflow's client-side environment supports Stripe.js initialization and UI event handling, but both Checkout Sessions and webhook processing require server-side implementation. See the Security requirements section for API key handling guidelines.

Custom code injection allows loading Stripe.js in the site-wide head section. Webflow supports up to 50,000 characters of custom code across Site settings, Page settings, Code Embed elements, and CMS Rich text fields.

At a high level, you'll:

  • Load Stripe.js in Webflow's head code section
  • Initialize with your publishable key (pk_test_* or pk_live_*)
  • Add event listeners that fetch Checkout Sessions from your backend middleware endpoint
  • Use the returned session ID with stripe.redirectToCheckout({ sessionId }) to redirect to Stripe-hosted checkout

<script src="https://js.stripe.com/v3/"></script>
<script>
  const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');

  document.getElementById('subscribe-btn').addEventListener('click', async () => {
    const response = await fetch('https://your-middleware.com/create-checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ priceId: 'price_metered_123' })
    });
    const { sessionId } = await response.json();
    await stripe.redirectToCheckout({ sessionId });
  });
</script>

Middleware webhook handling

Webhook processing requires server-side code with signature verification. Stripe signs every webhook payload, and your endpoint must verify this signature before processing events.

At a high level, you'll:

  • Create a publicly accessible HTTPS endpoint
  • Preserve the raw request body before any JSON parsing (required for signature verification)
  • Verify the signature using stripe.webhooks.constructEvent() with the raw body and webhook signing secret
  • Return a 2xx status immediately, then process asynchronously

Sample webhook handler (Node.js with ES modules):

import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export default async function handler(req, res) {
  const sig = req.headers['stripe-signature'];

  let event;
  try {
    // Raw body required for signature verification
    // Critical: Use express.raw({type: 'application/json'}) instead of express.json()
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Acknowledge immediately, process asynchronously
  res.status(200).json({ received: true });

  // Handle events asynchronously (see Stripe docs for event types)
  processEventAsync(event);
}

Critical webhook events for usage-based billing:

Event Purpose
invoice.created Track invoice generation for billing period
invoice.payment_succeeded Confirm payment and provision access
customer.subscription.updated Detect plan changes or cancellations
v1.billing.meter.error_report_triggered Monitor meter ingestion failures

Stripe retries failed webhooks for up to 3 days using exponential backoff. Implement idempotency in two ways: (1) include the Idempotency-Key header with unique values (recommended: UUID) when making outbound API requests to Stripe, and (2) track processed webhook event.id values in your database to prevent duplicate processing of inbound webhook events.

Usage tracking and reporting architecture

Your middleware must capture, aggregate, and report usage data to Stripe before each billing cycle closes. Stripe provides a default 1-hour invoice finalization grace period after the billing period ends, which is configurable up to 72 hours (3 days).

Data transformation example:

Application captures raw usage events:

{
  "user_id": "user_123",
  "action": "api_call",
  "endpoint": "/search",
  "timestamp": 1704824589
}

Aggregation service combines events by customer:

{
  "customer_id": "cus_ABC123",
  "api_calls_count": 1500,
  "period_start": 1704067200,
  "period_end": 1704758400
}

Submitted to Stripe as meter event:

{
  "event_name": "api_requests",
  "payload": {
    "value": "1500",
    "stripe_customer_id": "cus_ABC123"
  },
  "timestamp": 1704824589
}

Timestamp constraints from Stripe's recording usage documentation:

Idempotency implementation prevents double-billing during retries. According to Stripe's idempotency documentation, include a unique Idempotency-Key header with each usage record submission:

await stripe.billing.meterEvents.create({
  event_name: 'api_requests',
  payload: {
    stripe_customer_id: customerId,
    value: aggregatedUsage.toString()
  }
}, {
  idempotencyKey: `${customerId}-${billingPeriodStart}-${timestamp}`
});

Idempotency keys must be generated as unique identifiers (using UUIDs or deterministically from customer ID, billing period, and event timestamp) and included with POST requests to prevent duplicate processing. According to Stripe's idempotency documentation, keys automatically expire after 24 hours, and Stripe returns the same result for subsequent requests with identical keys within this window, preventing double-billing in distributed systems.

Middleware platform options

Several platforms can serve as the middleware layer between Webflow and Stripe.

Serverless functions enable full customization of webhook processing logic, though they require custom code implementation.

Platform Throughput Best For
Cloudflare Workers No general request-per-second limit (Stripe API limits apply) Low-latency, global distribution at edge locations
AWS Lambda Function URLs with standard rate limits AWS ecosystem integration
Vercel Functions API routes with Git-based deployment Modern JavaScript applications

No-code platforms offer visual workflow builders:

  • Make.com provides native Stripe webhook triggers with visual data mapping, but requires custom code modules for proper signature verification
  • Zapier uses Stripe's API triggers rather than raw webhooks; for webhook-based integrations, external signature verification middleware is recommended

For production systems processing financial data, webhook handlers must implement explicit signature verification using platform-specific methods (Stripe's constructEvent or constructEventAsync) and maintain idempotent processing patterns with event ID tracking, database transaction atomicity, and asynchronous event handling to ensure reliable billing operations.

Webflow CMS synchronization

Synchronizing Stripe subscription state with Webflow CMS enables dynamic content gating and personalization.

At a high level, you'll:

  • Create a CMS collection to store customer subscription data
  • Map Stripe webhook events to Webflow CMS API operations
  • Handle rate limits when making CMS API requests

Data shape illustration:

Stripe subscription webhook payload (source):

{
  "id": "sub_1234567890",
  "customer": "cus_ABC123",
  "status": "active",
  "items": {
    "data": [{
      "price": { "id": "price_XYZ", "unit_amount": 2000 }
    }]
  },
  "current_period_end": 1735689600
}

Webflow CMS field structure (target):

{
  "fieldData": {
    "stripe-customer-id": "cus_ABC123",
    "subscription-status": "active",
    "plan-name": "Pro Plan",
    "renewal-date": "2025-01-31"
  }
}

Field mapping transformation:

  • subscription.customerstripe-customer-id (direct mapping)
  • subscription.statussubscription-status (direct mapping)
  • subscription.items.data[0].price.idplan-name (requires lookup table)
  • subscription.current_period_endrenewal-date (timestamp to date conversion)

Sample sync pattern:

// Server-side webhook handler - calls Webflow CMS API after signature verification
if (event.type === 'customer.subscription.updated') {
  const subscription = event.data.object;

  await fetch(`https://api.webflow.com/v2/collections/${collectionId}/items`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.WEBFLOW_API_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      fieldData: {
        'stripe-customer-id': subscription.customer,
        'subscription-status': subscription.status,
        'renewal-date': new Date(subscription.current_period_end * 1000).toISOString().split('T')[0]
      }
    })
  });
}

The Webflow CMS API supports bulk operations of up to 100 items per request, enabling efficient synchronization of large customer datasets by batching create, update, and delete operations into single API calls while staying within rate limit constraints

Alex Halliday
CEO
AirOps
Learn more
Aleyda Solis
International SEO Consultant and Founder
Orainti
Learn more
Barry Schwartz
President and Owner
RustyBrick, Inc
Learn more
Chris Andrew
CEO and Cofounder
Scrunch
Learn more
Connor Gillivan
CEO and Founder
TrioSEO
Learn more
Eli Schwartz
Author
Product-led SEO
Learn more
Ethan Smith
CEO
Graphite
Learn more
Evan Bailyn
CEO
First Page Sage
Learn more
Gaetano Nino DiNardi
Growth Advisor
Learn more
Jason Barnard
CEO and Founder
Kalicube
Learn more
Kevin Indig
Growth Advisor
Learn more
Lily Ray
VP SEO Strategy & Research
Amsive
Learn more
Marcel Santilli
CEO and Founder
GrowthX
Learn more
Michael King
CEO and Founder
iPullRank
Learn more
Rand Fishkin
CEO and Cofounder
SparkToro, Alertmouse, & Snackbar Studio
Learn more
Stefan Katanic
CEO
Veza Digital
Learn more
Steve Toth
CEO
Notebook Agency
Learn more
Sydney Sloan
CMO
G2
Learn more

Checkout Session creation

Checkout Sessions must be created server-side in your backend API or serverless functions using secret API keys, then the session object is returned to the Webflow frontend to initiate the redirect to Stripe's hosted checkout.

According to Stripe's Checkout Sessions API, metered subscriptions require specific configuration. For metered billing, do not pass quantity in the line items:

// Server-side only - creates session and returns ID to frontend
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: 'price_metered_monthly' }],
  success_url: 'https://your-webflow-site.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://your-webflow-site.com/pricing'
});

// Frontend then calls: stripe.redirectToCheckout({ sessionId: session.id })

The success_url and cancel_url point back to your Webflow site. Use the {CHECKOUT_SESSION_ID} template variable to retrieve session details on your success page using the Stripe API.

For metered billing, configure your price with recurring.usage_type: 'metered' in the Stripe Dashboard or via the Prices API.

Customer Portal integration

Stripe's Customer Portal allows subscribers to manage their billing independently. Like Checkout Sessions, portal sessions require server-side creation using secret API keys.

At a high level, you'll:

  • Call your backend server endpoint to create a portal session with the customer ID using your secret API key
  • Redirect to the returned url from the portal session response
  • Configure portal settings in the Stripe Dashboard to control available features
// Server-side: create portal session
const portalSession = await stripe.billingPortal.sessions.create({
  customer: customerId,
  return_url: 'https://your-webflow-site.com/account',
});
// Return portalSession.url to frontend

// Frontend: redirect user
window.location.href = portalSession.url;

The Customer Portal is hosted by Stripe and handles subscription management. Your backend creates portal sessions; users are then redirected to Stripe's interface.

Testing and development environment

Stripe provides comprehensive testing tools that simulate the full billing lifecycle without processing real payments. Proper testing infrastructure ensures you can validate webhook handlers, billing logic, and subscription flows before deploying to production.

At a high level, you'll:

  • Use test API keys (pk_test_*, sk_test_*) in development
  • Test webhooks locally with the Stripe CLI
  • Use test clocks to simulate billing cycle progression

Test mode configuration:

  • Use test API keys (pk_test_*, sk_test_*) in development
  • Test card numbers simulate various scenarios (see Stripe testing documentation)
  • Test mode rejects real card numbers

Local webhook testing with Stripe CLI:

# Install and authenticate
stripe login

# Forward webhooks to local endpoint
stripe listen --forward-to localhost:4242/webhook

# Trigger test events
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.updated

The Stripe CLI provides a local webhook signing secret for testing webhook events during development. When you run stripe listen --forward-to localhost:4242/webhook, the CLI generates a temporary webhook signing secret for that session that you must use to verify webhook signatures in your local development environment. This signing secret differs from your production webhook signing secrets, which are registered in the Stripe Dashboard and used for live webhook verification. Always keep webhook signing secrets secure and store them in environment variables, never in source code.

Test clocks simulate time progression for subscription testing. According to Stripe's test clocks documentation, you can advance time to trigger billing cycle events without waiting.

Webflow staging environment:

  • Deploy to Webflow staging subdomain for testing
  • Configure staging-specific webhook endpoints in Stripe
  • Use test API keys in staging environment

Error handling patterns

Both Stripe and Webflow return structured error responses that require specific handling.

At a high level, you'll:

  • Handle Stripe error codes with appropriate retry logic
  • Implement exponential backoff for rate limits (HTTP 429)
  • Configure CORS headers for cross-origin requests from Webflow

Stripe error categories from Stripe's error codes documentation:

HTTP Status Error Type Action
400 invalid_request_error Fix request parameters
401 Authentication error Verify or regenerate API key
402 card_error Prompt customer for different payment method
429 Rate limit exceeded Implement exponential backoff and retry

Webflow CMS API error responses from Webflow's Error Handling Documentation use standard HTTP status codes to indicate error conditions:

  • DuplicateValue: Item already exists in the collection
  • Forbidden: Insufficient API permissions or access rights
  • ResourceMissing: Requested collection, item, or user resource not found

CORS configuration is required in your serverless function responses when handling API calls from Webflow. Include these headers in all responses:

return {
  statusCode: 200,
  headers: {
    'Access-Control-Allow-Origin': process.env.WEBFLOW_DOMAIN || '*',
    'Access-Control-Allow-Headers': 'Content-Type, Stripe-Signature',
    'Access-Control-Allow-Methods': 'POST, OPTIONS'
  },
  body: JSON.stringify(data)
};

Security requirements

Payment integrations require strict security practices across all components. Refer to the detailed security requirements in this section for complete API key management, webhook verification, and PCI compliance guidelines.

At a high level, you'll:

  • Use publishable keys (pk_*) exclusively in frontend code like Webflow
  • Isolate secret keys (sk_*) to server-side environments only
  • Verify webhook signatures using HMAC SHA-256 with endpoint secrets (whsec_*)
  • Ensure all connections use HTTPS with TLS 1.2 or higher
  • Acknowledge webhook events immediately with 2xx status before processing
  • Use Stripe.js and Elements to minimize PCI compliance scope

API key management:

  • Publishable keys (pk_*): Safe for Webflow custom code
  • Secret keys (sk_*): Only in middleware environment variables
  • Restricted keys: Use when possible to limit access scope
  • Webhook secrets (whsec_*): Store in environment variables

According to Stripe's Integration Security Guide, using Stripe.js with Elements ensures card data flows directly to Stripe without touching your infrastructure, which is the recommended approach for Webflow integrations and significantly minimizes PCI compliance scope.

Webhook signature verification is mandatory, not optional. The Stripe webhook signature verification documentation specifies HMAC SHA-256 validation using the endpoint secret.

Environment isolation:

  • Separate Stripe accounts or API key sets for test and production
  • Different webhook endpoints per environment
  • Environment-specific secrets management

Authentication considerations

Webflow's native User Accounts feature was deprecated on January 29, 2026, requiring migration to third-party authentication solutions like Memberstack, Outseta, Auth0, or Clerk. Your backend must correlate authenticated users with Stripe customer IDs using these third-party authentication providers. Store this mapping in your database and use it when creating Checkout Sessions server-side with your secret API key:

const session = await stripe.checkout.sessions.create({
  customer: existingStripeCustomerId, // or use customer_email for new customers
  client_reference_id: yourInternalUserId,
  // ... other configuration
});

The client_reference_id appears in Checkout-related webhook payloads (such as checkout.session.completed), enabling you to link Stripe events back to your user database.

Implementation checklist

Before deploying to production, verify these requirements:

Middleware layer:

  • Webhook endpoint uses HTTPS with TLS 1.2+
  • Signature verification implemented with stripe.webhooks.constructEvent() using webhook signing secret
  • API key storage follows security requirements (see Security Requirements section)
  • CORS headers configured in serverless function response for Webflow domain requests
  • Idempotency keys generated with UUIDs for all POST requests to Stripe
  • Error handling returns appropriate HTTP status codes (2xx for success, 400 for validation errors, 429 for rate limits)

Webflow frontend:

  • Stripe.js loaded with publishable key only (pk_test_ or pk_live_)
  • Event handlers call middleware endpoints (serverless functions), never Stripe directly
  • Success/cancel URLs configured for post-payment routing
  • No secret keys (sk_) in custom code sections or hardcoded anywhere in Webflow

Stripe configuration:

  • Metered pricing configured on subscription products
  • Billing meters created with correct aggregation
  • Webhook endpoints registered in Dashboard
  • Customer Portal settings configured
  • Test mode verified before production deployment

Data synchronization:

  • Implement usage data storage in Webflow CMS or external database
  • Configure webhook listeners for Stripe billing events (invoice.created, customer.subscription.updated)
  • Map Stripe event payloads to Webflow CMS field structures
  • Implement rate limit handling with exponential backoff retry logic for API calls (refer to Webflow rate limits)

The combination of Webflow's visual development capabilities, Stripe's billing infrastructure, and a properly architected middleware layer enables sophisticated usage-based SaaS billing without maintaining traditional server infrastructure.

Read now

Last Updated
February 17, 2026
Category

Related articles


Get started for free

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.

Get started — it’s free
Watch demo

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.