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_*orpk_live_*) for frontend initialization - Stripe secret key (
sk_test_*orsk_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:
- Webflow handles UI presentation and initializes Stripe.js with publishable keys
- Middleware receives requests from Webflow, authenticates with Stripe using secret keys, and processes webhook events
- Stripe manages payment processing, hosted checkout pages, and billing meter aggregation
- 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_nameanddefault_aggregationconfiguration - Send meter events to
POST /v1/billing/meter_eventswith 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_*orpk_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:
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:
- Timestamps must be within the past 35 calendar days
- Cannot be more than 5 minutes in the future (to account for clock drift)
- A 1-hour default grace period exists after billing period ends for final usage submission (configurable up to 72 hours)
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.
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.customer→stripe-customer-id(direct mapping)subscription.status→subscription-status(direct mapping)subscription.items.data[0].price.id→plan-name(requires lookup table)subscription.current_period_end→renewal-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.


















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
urlfrom 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.updatedThe 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:
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 collectionForbidden: Insufficient API permissions or access rightsResourceMissing: 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_orpk_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.




