You’ve designed the perfect usage-based pricing model in the simulator. Now it’s time to make it operational—automatically calculate bills from real usage data and send them to Stripe, your internal billing system, or wherever you need them.
Why Usage-Based Billing Matters
Traditional subscription billing doesn’t work for AI products where costs vary 10x between customers. Usage-based billing ensures you:
Bill for actual consumption — Charge based on spans, tokens, sessions, or any telemetry metric
Match revenue to costs — Your pricing scales with the value (and cost) delivered
Prevent surprise bills — Real-time threshold alerts when customers approach spending limits
Automate invoicing — Generate usage-based invoices automatically at period end
The usage-based billing system converts your telemetry data into invoices without any manual work.
How It Works
Once you activate a pricing model, the billing system automatically tracks usage and triggers events based on your configuration:
Telemetry Data Pricing Engine Billing Events
───────────────── → ────────────────── → ─────────────────
• Spans (with tenant_id) 1. Aggregate usage • billing.period_end
• Sessions 2. Apply pricing model → Stripe webhook
• Tokens 3. Calculate charges • billing.threshold_reached
• Logs 4. Trigger webhooks → Slack alert
5. Update state • billing.usage_spike
→ Customer email
The system handles:
Period-based billing (monthly, weekly, custom)
Threshold monitoring (alert when customer hits $500, 1M spans, etc.)
Usage spike detection (notify on 2x normal usage)
Shadow mode reporting (test billing logic without charging customers)
Event Types
Configure webhooks to trigger on these billing lifecycle events:
billing.period_end Fires at the end of each billing period (monthly, weekly, custom). Use this to generate invoices and sync with Stripe.
billing.threshold_reached Real-time alert when usage crosses a defined limit (e.g., $500 spend, 1M spans). Prevents surprise bills.
billing.usage_spike Detects abnormal usage patterns (2x+ normal usage). Helps catch runaway costs or billing anomalies.
billing.manual_export User-triggered export for ad-hoc reporting, customer inquiries, or manual reconciliation.
Setup Guide
1. Activate Your Pricing Model
Before billing can work, ensure your pricing model is active (not in shadow mode):
// Via API
await client . pricingModels . update ({
modelId: "plan_ent_premium_001" ,
status: "active"
});
// Or via dashboard
// Navigate to Pricing Models → Select Model → Click "Activate"
Shadow mode vs. Active mode : Shadow mode generates reports but doesn’t trigger billing webhooks. Use it for testing. Active mode sends real billing events.
Set up webhook URLs to receive billing events:
Endpoint: POST /billing/webhooks
{
"webhook_id" : "wh_stripe_prod" ,
"name" : "Stripe Production Webhook" ,
"url" : "https://api.yourcompany.com/webhooks/billing" ,
"events" : [
"billing.period_end" ,
"billing.threshold_reached" ,
"billing.usage_spike"
],
"auth" : {
"type" : "hmac_sha256" ,
"secret" : "whsec_your_webhook_secret"
},
"metadata" : {
"environment" : "production" ,
"team" : "finance"
}
}
Response:
{
"webhook_id" : "wh_stripe_prod" ,
"status" : "active" ,
"created_at" : "2024-03-01T10:00:00Z" ,
"test_url" : "/billing/webhooks/wh_stripe_prod/test"
}
Every telemetry record must include customer_id for cost attribution. Decorate at ingestion time:
// When logging spans/sessions/events
await telemetry . track ({
type: "span" ,
span_id: "span_abc123" ,
trace_id: "trace_xyz789" ,
customer_id: "cust_acme_corp" , // ← Required for billing
usage_total_tokens: 50000 ,
model: "gpt-4o"
});
Critical : Telemetry without customer_id cannot be billed. Ensure your SDK or ingestion pipeline adds this field to every event.
Define when events should fire:
Period-based billing:
{
"trigger_type" : "period_end" ,
"schedule" : {
"frequency" : "monthly" ,
"day_of_month" : 1 ,
"timezone" : "America/Los_Angeles"
},
"pricing_model_id" : "plan_ent_premium_001"
}
Threshold alerts:
{
"trigger_type" : "threshold_reached" ,
"threshold" : {
"metric" : "total_amount" ,
"value" : 500.00 ,
"currency" : "USD"
},
"notification_channels" : [ "webhook" , "email" ],
"pricing_model_id" : "plan_ent_premium_001"
}
Usage spike detection:
{
"trigger_type" : "usage_spike" ,
"spike_config" : {
"multiplier" : 2.0 , // Alert on 2x normal usage
"baseline_period" : "7d" , // Compare to 7-day average
"cooldown" : "1h" // Don't alert more than once per hour
}
}
5. Test Your Webhooks
Before going live, test your webhook endpoints:
curl -X POST https://api.yourplatform.com/v1/billing/webhooks/wh_stripe_prod/test
This sends a sample billing.period_end event to verify your endpoint is configured correctly.
Webhook Payload Structure
billing.period_end
Fires at the end of each billing period with usage summary and charges:
{
"id" : "evt_billing_period_end_001" ,
"type" : "billing.period_end" ,
"created_at" : "2024-03-31T23:59:59Z" ,
"data" : {
"customer_id" : "cust_acme_corp" ,
"period" : {
"start" : "2024-03-01T00:00:00Z" ,
"end" : "2024-03-31T23:59:59Z"
},
"pricing_model" : {
"model_id" : "plan_ent_premium_001" ,
"version" : "1.2.0" ,
"name" : "Enterprise Usage-Based Plan"
},
"summary" : {
"total_amount" : 1450.00 ,
"currency" : "USD" ,
"subtotals" : {
"rules_total" : 950.00 ,
"platform_fees_total" : 500.00 ,
"discounts_total" : 0.00
}
},
"breakdown" : [
{
"rule_id" : "rule_spans_001" ,
"display_name" : "Distributed Tracing Spans" ,
"usage" : {
"raw_value" : 5000000 ,
"unit" : "spans"
},
"amount" : 200.00
},
{
"rule_id" : "rule_session_001" ,
"display_name" : "User Sessions" ,
"usage" : {
"raw_value" : 50000 ,
"unit" : "sessions"
},
"amount" : 500.00
}
],
"usage_summary" : {
"sessions" : 50000 ,
"traces" : 200000 ,
"spans" : 5000000 ,
"raw_events" : 1000000 ,
"raw_bytes" : 5368709120
}
}
}
billing.threshold_reached
Real-time alert when a customer crosses a usage or spend threshold:
{
"id" : "evt_threshold_reached_001" ,
"type" : "billing.threshold_reached" ,
"created_at" : "2024-03-15T14:23:45Z" ,
"data" : {
"customer_id" : "cust_acme_corp" ,
"threshold" : {
"metric" : "total_amount" ,
"limit" : 500.00 ,
"current_value" : 520.00 ,
"currency" : "USD"
},
"period" : {
"start" : "2024-03-01T00:00:00Z" ,
"current" : "2024-03-15T14:23:45Z"
},
"pricing_model" : {
"model_id" : "plan_ent_premium_001" ,
"version" : "1.2.0"
},
"usage_snapshot" : {
"sessions" : 28500 ,
"spans" : 2850000 ,
"tokens" : 285000000
}
}
}
billing.usage_spike
Detects abnormal usage patterns:
{
"id" : "evt_usage_spike_001" ,
"type" : "billing.usage_spike" ,
"created_at" : "2024-03-15T10:00:00Z" ,
"data" : {
"customer_id" : "cust_acme_corp" ,
"spike_detected" : {
"metric" : "spans" ,
"baseline_avg" : 150000 ,
"current_value" : 350000 ,
"multiplier" : 2.33 ,
"severity" : "high"
},
"period" : {
"baseline" : "2024-03-08T00:00:00Z to 2024-03-14T23:59:59Z" ,
"spike" : "2024-03-15T00:00:00Z to 2024-03-15T10:00:00Z"
},
"projected_impact" : {
"if_continued" : 4200.00 ,
"currency" : "USD" ,
"vs_normal" : "+180%"
}
}
}
Authentication & Security
HMAC-SHA256 Signature Verification
All webhook requests include an HMAC signature in the X-Webhook-Signature header. Verify this to ensure requests are authentic:
Header format:
X-Webhook-Signature: t=1709294400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Verification code (Node.js):
import crypto from 'crypto' ;
function verifyWebhookSignature (
payload : string ,
signature : string ,
secret : string
) : boolean {
const [ timestampPart , signaturePart ] = signature . split ( ',' );
const timestamp = timestampPart . split ( '=' )[ 1 ];
const expectedSignature = signaturePart . split ( '=' )[ 1 ];
const signedPayload = ` ${ timestamp } . ${ payload } ` ;
const computedSignature = crypto
. createHmac ( 'sha256' , secret )
. update ( signedPayload )
. digest ( 'hex' );
return crypto . timingSafeEqual (
Buffer . from ( expectedSignature ),
Buffer . from ( computedSignature )
);
}
// Usage in your webhook handler
app . post ( '/webhooks/billing' , ( req , res ) => {
const signature = req . headers [ 'x-webhook-signature' ];
const rawBody = JSON . stringify ( req . body );
if ( ! verifyWebhookSignature ( rawBody , signature , process . env . WEBHOOK_SECRET )) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
// Process the webhook event
const event = req . body ;
console . log ( `Received ${ event . type } for customer ${ event . data . customer_id } ` );
res . json ({ received: true });
});
Python verification:
import hmac
import hashlib
import time
def verify_webhook_signature ( payload : str , signature : str , secret : str ) -> bool :
timestamp, expected_sig = signature.split( ',' )
timestamp = timestamp.split( '=' )[ 1 ]
expected_sig = expected_sig.split( '=' )[ 1 ]
signed_payload = f " { timestamp } . { payload } "
computed_sig = hmac.new(
secret.encode( 'utf-8' ),
signed_payload.encode( 'utf-8' ),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_sig, computed_sig)
Replay Attack Prevention
Check the timestamp to prevent replay attacks:
const MAX_TIMESTAMP_AGE = 5 * 60 ; // 5 minutes
function isTimestampValid ( timestamp : string ) : boolean {
const currentTime = Math . floor ( Date . now () / 1000 );
const eventTime = parseInt ( timestamp , 10 );
return Math . abs ( currentTime - eventTime ) <= MAX_TIMESTAMP_AGE ;
}
Common Integration Patterns
1. Stripe Integration
Forward billing events to Stripe for invoice generation:
import Stripe from 'stripe' ;
const stripe = new Stripe ( process . env . STRIPE_SECRET_KEY );
app . post ( '/webhooks/billing' , async ( req , res ) => {
const event = req . body ;
if ( event . type === 'billing.period_end' ) {
const { customer_id , summary , breakdown , period } = event . data ;
// Create Stripe invoice
const invoice = await stripe . invoices . create ({
customer: customer_id ,
collection_method: 'charge_automatically' ,
metadata: {
pricing_model_id: event . data . pricing_model . model_id ,
period_start: period . start ,
period_end: period . end
}
});
// Add line items from breakdown
for ( const item of breakdown ) {
await stripe . invoiceItems . create ({
customer: customer_id ,
invoice: invoice . id ,
amount: Math . round ( item . amount * 100 ), // Stripe uses cents
currency: summary . currency . toLowerCase (),
description: ` ${ item . display_name } ( ${ item . usage . raw_value . toLocaleString () } ${ item . usage . unit } )`
});
}
// Add platform fee
if ( summary . subtotals . platform_fees_total > 0 ) {
await stripe . invoiceItems . create ({
customer: customer_id ,
invoice: invoice . id ,
amount: Math . round ( summary . subtotals . platform_fees_total * 100 ),
currency: summary . currency . toLowerCase (),
description: 'Platform Fee'
});
}
// Finalize and send
await stripe . invoices . finalizeInvoice ( invoice . id );
console . log ( `Stripe invoice created: ${ invoice . id } ` );
}
res . json ({ received: true });
});
2. Threshold Alerts to Slack
Notify your team when customers hit usage limits:
import { WebClient } from '@slack/web-api' ;
const slack = new WebClient ( process . env . SLACK_BOT_TOKEN );
app . post ( '/webhooks/billing' , async ( req , res ) => {
const event = req . body ;
if ( event . type === 'billing.threshold_reached' ) {
const { customer_id , threshold , usage_snapshot } = event . data ;
await slack . chat . postMessage ({
channel: '#billing-alerts' ,
text: `🚨 Customer ${ customer_id } reached ${ threshold . currency } ${ threshold . current_value } (limit: ${ threshold . limit } )` ,
blocks: [
{
type: 'section' ,
text: {
type: 'mrkdwn' ,
text: `*Billing Threshold Alert* \n\n Customer: \` ${ customer_id } \`\n Current spend: *$ ${ threshold . current_value } * \n Threshold: $ ${ threshold . limit } `
}
},
{
type: 'section' ,
fields: [
{ type: 'mrkdwn' , text: `*Sessions:* \n ${ usage_snapshot . sessions . toLocaleString () } ` },
{ type: 'mrkdwn' , text: `*Spans:* \n ${ usage_snapshot . spans . toLocaleString () } ` },
{ type: 'mrkdwn' , text: `*Tokens:* \n ${ usage_snapshot . tokens . toLocaleString () } ` }
]
}
]
});
}
res . json ({ received: true });
});
3. Custom Billing Database
Store billing events for internal reporting:
import { PrismaClient } from '@prisma/client' ;
const prisma = new PrismaClient ();
app . post ( '/webhooks/billing' , async ( req , res ) => {
const event = req . body ;
if ( event . type === 'billing.period_end' ) {
const { customer_id , period , summary , breakdown } = event . data ;
// Store invoice
const invoice = await prisma . invoice . create ({
data: {
customer_id ,
period_start: new Date ( period . start ),
period_end: new Date ( period . end ),
total_amount: summary . total_amount ,
currency: summary . currency ,
pricing_model_id: event . data . pricing_model . model_id ,
pricing_model_version: event . data . pricing_model . version ,
status: 'pending' ,
line_items: {
create: breakdown . map ( item => ({
rule_id: item . rule_id ,
description: item . display_name ,
quantity: item . usage . raw_value ,
unit: item . usage . unit ,
amount: item . amount
}))
}
}
});
console . log ( `Invoice ${ invoice . id } stored for customer ${ customer_id } ` );
}
res . json ({ received: true });
});
Prerequisites & Setup Checklist
Before enabling billing webhooks, ensure your system has:
✅ Customer ID Tagging
Every telemetry record must include customer_id:
// ❌ Bad: No customer_id
telemetry . track ({
type: "span" ,
span_id: "span_123"
});
// ✅ Good: Includes customer_id
telemetry . track ({
type: "span" ,
span_id: "span_123" ,
customer_id: "cust_acme_corp" // ← Required
});
Implementation approaches:
Add customer_id in your SDK/library initialization
Use middleware to inject customer_id from auth context
Decorate at ingestion time using API keys or auth tokens
✅ Pricing Model Assignment
Each customer needs an active pricing model. Store this in a state table:
Database schema example:
CREATE TABLE customer_pricing (
customer_id VARCHAR ( 255 ) PRIMARY KEY ,
pricing_model_id VARCHAR ( 255 ) NOT NULL ,
pricing_model_version VARCHAR ( 50 ) NOT NULL ,
activated_at TIMESTAMP NOT NULL ,
metadata JSONB
);
API to assign pricing model:
await client . customers . setPricingModel ({
customerId: "cust_acme_corp" ,
modelId: "plan_ent_premium_001"
});
✅ Webhook Endpoint
Your server must expose a POST endpoint to receive events:
// Express.js example
app . post ( '/webhooks/billing' ,
express . raw ({ type: 'application/json' }), // Important: raw body for signature verification
async ( req , res ) => {
try {
const event = JSON . parse ( req . body . toString ());
// Verify signature
const signature = req . headers [ 'x-webhook-signature' ];
if ( ! verifyWebhookSignature ( req . body . toString (), signature , process . env . WEBHOOK_SECRET )) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
// Process event
await handleBillingEvent ( event );
res . json ({ received: true });
} catch ( error ) {
console . error ( 'Webhook error:' , error );
res . status ( 500 ). json ({ error: 'Webhook processing failed' });
}
}
);
Critical : Always respond with 200 OK within 5 seconds to prevent webhook retries. Process heavy operations (Stripe API calls, database writes) asynchronously using a job queue.
✅ Idempotency Handling
Webhooks may be delivered multiple times. Implement idempotency:
async function handleBillingEvent ( event : BillingEvent ) {
// Check if we've already processed this event
const existing = await prisma . processedWebhook . findUnique ({
where: { event_id: event . id }
});
if ( existing ) {
console . log ( `Event ${ event . id } already processed, skipping` );
return ;
}
// Process event
await processBillingEvent ( event );
// Mark as processed
await prisma . processedWebhook . create ({
data: {
event_id: event . id ,
event_type: event . type ,
processed_at: new Date ()
}
});
}
Testing & Debugging
Shadow Mode Testing
Before going live, run your pricing model in shadow mode:
await client . pricingModels . update ({
modelId: "plan_ent_premium_001" ,
status: "shadow" // Generates reports but doesn't trigger billing webhooks
});
// Run simulations to verify calculations
const result = await client . simulations . run ({
modelId: "plan_ent_premium_001" ,
customerId: "cust_test_001" ,
timeRange: {
start: "2024-03-01T00:00:00Z" ,
end: "2024-03-31T23:59:59Z"
}
});
console . log ( `Shadow mode revenue: $ ${ result . summary . total_amount } ` );
Webhook Test Events
Send test events to your endpoint:
curl -X POST https://api.yourplatform.com/v1/billing/webhooks/wh_stripe_prod/test \
-H "Authorization: Bearer your_api_key"
Local Development with Webhook Forwarding
Use tools like ngrok or cloudflared to forward webhooks to localhost:
# Using ngrok
ngrok http 3000
# Update webhook URL to ngrok URL
curl -X PATCH https://api.yourplatform.com/v1/billing/webhooks/wh_stripe_prod \
-H "Authorization: Bearer your_api_key" \
-d '{"url": "https://abc123.ngrok.io/webhooks/billing"}'
Webhook Logs
View webhook delivery history and debug failures:
Endpoint: GET /billing/webhooks/{webhook_id}/deliveries
{
"data" : [
{
"delivery_id" : "del_001" ,
"event_type" : "billing.period_end" ,
"status" : "success" ,
"response_code" : 200 ,
"delivered_at" : "2024-03-31T23:59:59Z" ,
"latency_ms" : 245
},
{
"delivery_id" : "del_002" ,
"event_type" : "billing.threshold_reached" ,
"status" : "failed" ,
"response_code" : 500 ,
"error" : "Internal server error" ,
"delivered_at" : "2024-03-15T14:23:45Z" ,
"retry_count" : 3
}
]
}
Retry Failed Webhooks
Manually retry failed webhook deliveries:
curl -X POST https://api.yourplatform.com/v1/billing/webhooks/deliveries/del_002/retry \
-H "Authorization: Bearer your_api_key"
Billing API Reference
Webhook Management
Create Webhook
Endpoint: POST /billing/webhooks
Request:
{
"name" : "Production Stripe Webhook" ,
"url" : "https://api.yourcompany.com/webhooks/billing" ,
"events" : [ "billing.period_end" , "billing.threshold_reached" ],
"auth" : {
"type" : "hmac_sha256" ,
"secret" : "whsec_your_webhook_secret"
}
}
Response: 201 Created
{
"webhook_id" : "wh_abc123" ,
"status" : "active" ,
"created_at" : "2024-03-01T10:00:00Z"
}
List Webhooks
Endpoint: GET /billing/webhooks
Response: 200 OK
{
"data" : [
{
"webhook_id" : "wh_abc123" ,
"name" : "Production Stripe Webhook" ,
"url" : "https://api.yourcompany.com/webhooks/billing" ,
"events" : [ "billing.period_end" , "billing.threshold_reached" ],
"status" : "active" ,
"last_delivery_at" : "2024-03-31T23:59:59Z" ,
"success_rate" : 99.2
}
]
}
Update Webhook
Endpoint: PATCH /billing/webhooks/{webhook_id}
Request:
{
"events" : [ "billing.period_end" , "billing.threshold_reached" , "billing.usage_spike" ],
"status" : "active"
}
Delete Webhook
Endpoint: DELETE /billing/webhooks/{webhook_id}
Response: 200 OK
Billing Triggers
Create Period-Based Trigger
Endpoint: POST /billing/triggers
Request:
{
"trigger_type" : "period_end" ,
"schedule" : {
"frequency" : "monthly" ,
"day_of_month" : 1 ,
"timezone" : "America/Los_Angeles"
},
"pricing_model_id" : "plan_ent_premium_001" ,
"webhook_ids" : [ "wh_abc123" ]
}
Create Threshold Trigger
Endpoint: POST /billing/triggers
Request:
{
"trigger_type" : "threshold_reached" ,
"threshold" : {
"metric" : "total_amount" ,
"value" : 500.00 ,
"currency" : "USD"
},
"pricing_model_id" : "plan_ent_premium_001" ,
"webhook_ids" : [ "wh_abc123" ],
"notification_channels" : [ "webhook" , "email" ]
}
Create Usage Spike Trigger
Endpoint: POST /billing/triggers
Request:
{
"trigger_type" : "usage_spike" ,
"spike_config" : {
"multiplier" : 2.0 ,
"baseline_period" : "7d" ,
"cooldown" : "1h"
},
"pricing_model_id" : "plan_ent_premium_001" ,
"webhook_ids" : [ "wh_slack_alerts" ]
}
Customer Billing Management
Assign Pricing Model to Customer
Endpoint: POST /billing/customers/{customer_id}/pricing-model
Request:
{
"pricing_model_id" : "plan_ent_premium_001" ,
"effective_date" : "2024-04-01T00:00:00Z" ,
"metadata" : {
"contract_id" : "contract_123" ,
"sales_rep" : "alice@company.com"
}
}
Get Customer Billing Status
Endpoint: GET /billing/customers/{customer_id}/status
Response: 200 OK
{
"customer_id" : "cust_acme_corp" ,
"pricing_model" : {
"model_id" : "plan_ent_premium_001" ,
"version" : "1.2.0" ,
"activated_at" : "2024-03-01T00:00:00Z"
},
"current_period" : {
"start" : "2024-03-01T00:00:00Z" ,
"end" : "2024-03-31T23:59:59Z" ,
"days_remaining" : 5
},
"usage_to_date" : {
"sessions" : 28500 ,
"spans" : 2850000 ,
"tokens" : 285000000
},
"projected_bill" : {
"amount" : 875.00 ,
"currency" : "USD" ,
"based_on" : "current_usage_rate"
},
"thresholds" : [
{
"metric" : "total_amount" ,
"limit" : 500.00 ,
"current" : 520.00 ,
"status" : "exceeded" ,
"exceeded_at" : "2024-03-15T14:23:45Z"
}
]
}
Best Practices
Always Verify Webhook Signatures
Never trust webhook payloads without signature verification. Implement HMAC-SHA256 verification and check timestamp freshness to prevent replay attacks.
Respond Quickly, Process Async
Respond with 200 OK within 5 seconds to prevent retries. Use job queues (Celery, Bull, SQS) for heavy operations like Stripe API calls or database writes.
Webhooks can be delivered multiple times. Always check if you’ve already processed an event_id before taking action. Store processed event IDs in your database.
Test in Shadow Mode First
Before activating billing, run your pricing model in shadow mode for at least one full billing cycle. Verify calculations match expectations.
Monitor Webhook Success Rates
Track webhook delivery success rates and latency. Alert on success rates below 95% or sudden spikes in failures. This often indicates endpoint issues.
Tag Every Telemetry Record
Ensure every span, session, and event includes customer_id at ingestion time. Missing customer_id means unbillable usage—that’s lost revenue.
Configure threshold alerts at 50%, 80%, and 100% of expected monthly spend. This prevents surprise bills and gives customers visibility into usage.
Version Your Pricing Models
When changing pricing, create a new version rather than modifying the active model. This creates an audit trail and makes rollbacks easy.
Troubleshooting
Webhooks Not Firing
Problem : Activated pricing model but no webhooks are sent.
Solutions :
Verify pricing model status is “active” (not “shadow” or “draft”)
Check webhook status is “active” in /billing/webhooks
Confirm telemetry records have customer_id field
Review webhook delivery logs: GET /billing/webhooks/{webhook_id}/deliveries
Missing Customer ID
Problem : Telemetry data exists but billing calculations return $0.
Solutions :
Query telemetry data: SELECT COUNT(*) FROM spans WHERE customer_id IS NULL
Update SDK/library to include customer_id in all tracking calls
Add middleware to inject customer_id from auth context
Backfill historical data if possible (contact support for bulk update)
Webhook Signature Verification Fails
Problem : Endpoint returns 401 “Invalid signature”.
Solutions :
Ensure you’re using raw request body (not parsed JSON) for signature verification
Check webhook secret matches dashboard value
Verify timestamp is within 5 minutes (prevent replay attacks)
Test with sample payload: POST /billing/webhooks/{webhook_id}/test
Duplicate Invoices
Problem : Same billing period generates multiple invoices.
Solutions :
Implement idempotency: check if event.id was already processed
Store processed event IDs in database with unique constraint
Respond with 200 OK even if already processed (prevents retries)
Usage Spike False Positives
Problem : Spike alerts triggered during expected high usage (e.g., product launch).
Solutions :
Adjust baseline_period to include recent high-usage days
Increase multiplier from 2.0x to 3.0x or higher
Add cooldown period to prevent alert spam
Temporarily disable trigger: PATCH /billing/triggers/{trigger_id} {"status": "paused"}
Next Steps
Pro tip : Set up billing webhooks early, even if you’re not charging customers yet. Running in shadow mode lets you validate your billing logic with real usage data before going live.