E

Glossary

Event Ingestion

Event ingestion is the write path that carries usage records from a product into a billing system's durable storage. It covers the transport, the authentication, the field-level validation each record has to pass, and the acknowledgement returned to the sender. Measurement, rating, and invoicing all run after ingestion finishes.

Key Takeaways


  • Transport sets usage latency. Stripe's S3 connector surfaces events about five minutes after a file lands; its API v2 stream takes 10,000 events per second and shows up in seconds.

  • A 200 doesn't mean the event is valid. Stripe validates meter events asynchronously and reports failures through v1.billing.meter.error_report_triggered, which nobody sees without a subscriber.

  • Batching trades throughput for blast radius. Lago caps a batch at 100 events and 422s the entire request over one bad record, persisting nothing.

  • Stripe rejects any meter event whose timestamp falls outside a window running 35 calendar days back and 5 minutes forward, which makes clock drift on the emitting service a billing bug.

  • Adding a property can start dropping events. Stripe accepts 10,000 unique dimension combinations per meter per hour and 100 per customer; anything new past those ceilings is invalid.

Which transport should you send usage events over?

Pick by two properties: how fast a balance has to update, and how cheaply you can resend a window that went wrong.

Transport

Published ceiling

Usage visible after

Resending a bad window

One API call per event

1,000 calls per second per Stripe account, one concurrent call per customer per meter

Seconds, once async processing catches up

Resend each record under its original identifier

Batched HTTP request

Lago accepts 100 events per request

Seconds to minutes, set by your flush interval

Resend the batch; one bad record 422s all 100

High-throughput stream

10,000 events per second on Stripe's API v2 stream, 200,000 by arrangement

Seconds

Awkward: Stripe keeps stream requests out of Workbench logs

Object store or warehouse sync

Stripe's S3 connector polls 50 files or 10 GB at 10,000 events per second

Around five minutes after the file lands

Cheapest of the four: upload a corrected file

The warehouse path is slower than its throughput number suggests. Stripe's own worked example runs 100 files of 100,000 records each, 10 million events, and puts end-to-end processing at roughly 17 minutes. Fine for a monthly invoice, useless for a live balance.

Webhook-sourced ingestion sits apart: a third party owns the emitter, so you inherit its retry policy and its ordering, and late-arriving events stop being something you control.

Running two transports at once is normal, and I'd take that over agonizing over the choice. Stream what gates access, sync the warehouse nightly for the accounting truth, and give both the same identifier so deduplication collapses the overlap.

What does the sending service have to guarantee?

The sending service owns the parts of a record the billing system can't reconstruct: identity, customer, time, and value. Nearly every documented rejection traces back to one of them.

  • A stable identifier across retries. The idempotency key has to survive a process restart. Generate it fresh on each attempt and the dedupe window has nothing to match on.

  • A customer key that resolves. Stripe returns meter_event_customer_not_found when the key points at nothing and meter_event_no_customer_defined when the payload omits it. Both look identical from your side: usage that never appears.

  • A timestamp inside the accepted window. Stripe requires it within the past 35 calendar days and no more than 5 minutes ahead, documenting that allowance as headroom for clock drift.

  • A numeric value under the key the meter reads. Miss it and you get meter_event_value_not_found; send something unparseable and you get meter_event_invalid_value.

None of those rejections arrive in the HTTP response. Stripe publishes them asynchronously as v1.billing.meter.error_report_triggered and v1.billing.meter.no_meter_found events, so a stored record and a discarded one look the same at the call site. Subscribing is the difference between catching a gap in an hour and catching it on an invoice.

Schema evolution breaks the contract quietly. Stripe accepts at most 10,000 unique dimension combinations per meter per hour and 100 per customer. Combinations already counted keep processing; anything new past the ceiling returns meter_event_dimension_count_too_high. Ship a region or model_version field and your busiest accounts break first: high-cardinality metering arriving through the front door.

Related terms

Six entries that pick up where the acknowledgement leaves off:

FAQ


Can you switch ingestion transports without re-onboarding customers?

Yes, provided event identity stays stable across both paths. A billing system keys off the identifier and the customer reference, not the endpoint the record arrived through, so moving from single calls to a streamed path keeps the history intact. The risk sits in the cutover, where in-flight retries overlap the new path's first writes.


How many events per second can a billing system accept?

Published ceilings vary by an order of magnitude between endpoints at the same vendor. Stripe's v1 meter event endpoint allows 1,000 calls per second per account, its API v2 stream takes 10,000 per second with 200,000 by arrangement, and its S3 connector processes 10,000 per second. Read the limit for the transport you'll use.


What happens to an event a billing system rejects?

It's gone, and recovering it is your job. Stripe reports rejected records through error webhooks and expects you to correct and resend them, which only works if your service still holds the payload. Emit-and-forget services have nothing to resend, so the rejection becomes unbilled usage.


Does event ingestion need its own authentication path?

Sometimes, and high-throughput ingestion is where it shows up. Stripe's API v2 stream won't take a standard secret key: you create a Meter Event Session, get a token valid for 15 minutes, and refresh it before it expires. Any producer there needs refresh logic.

Back to glossary

Get Instant Feedback on Your Pricing | Join the Flexprice Community with 400+ Builders on Slack

Join the Flexprice Community on Slack