Usage-based billing fails quietly.
Not at checkout. Not on the pricing page. Not when the plan is created.
It fails when product activity and billable activity drift apart.
An event gets sent twice. A metric name stops matching the billing model. A queue retries without idempotency. A developer thinks they are tracking "usage" when they are really just logging product analytics.
That is how teams end up with billing systems they do not trust.
At Commet, usage ingestion is built around a simpler rule:
Track the product event that should create billable consumption, and make that mapping explicit.
Billing events are not analytics events
This distinction matters more than most teams think.
Analytics events answer questions like:
- which feature is popular?
- where do users drop off?
- what did the user click?
Billing events answer a different question:
What happened that should affect what this customer owes or has consumed?
Those are not interchangeable systems.
If your billing logic depends on analytics-style event streams with vague names and inconsistent semantics, you are going to create reconciliation work later.
The Commet model: feature event code -> usage event -> billing
In Commet, every metered feature has an event code.
That code is the contract between your product and your billing model.
Examples:
api_callsstorage_gbemails_sentcompute_minutes
When your application records usage against that code, Commet aggregates it for billing over the subscription period.
That gives you a direct path from product action to billable usage without inventing a second translation layer.
Track a single event when the product action happens
If one user action maps cleanly to one billable event, the simplest thing is usually the right thing.
await commet.usage.track({
externalId: "user_123",
feature: "api_calls",
value: 1,
idempotencyKey: "req_abc123",
})This is the core idea:
- identify the customer
- identify the billable feature
- send the quantity consumed
- make retries safe
That is enough to keep the billing model explicit and inspectable.
Batch when throughput matters
When volume increases, you do not want event ingestion to become a bottleneck.
Commet supports batch tracking for the same reason product teams batch other write-heavy operations: better efficiency, less overhead, simpler ingestion pipelines.
await commet.usage.trackBatch({
events: [
{ externalId: "user_123", feature: "api_calls", value: 1 },
{ externalId: "user_456", feature: "api_calls", value: 3 },
{ externalId: "user_123", feature: "storage_gb", value: 0.5 },
],
})If your product generates consumption in bursts, or you are syncing from internal jobs rather than direct user requests, batching is usually the right operational choice.
Idempotency is not optional
If you only remember one thing about usage ingestion, make it this:
Retries are normal. Duplicate billing is not.
Networks fail. Workers retry. Queues redeliver. Background jobs get resumed.
Without idempotency, every one of those normal system behaviors can become a billing defect.
That is why Commet supports idempotencyKey on usage tracking.
await commet.usage.track({
externalId: "user_123",
feature: "api_calls",
value: 1,
idempotencyKey: "job_7845_event_12",
})This gives you a reliable way to tell the billing system: "If this exact event already landed, do not count it twice."
That is the difference between a system you hope is correct and a system you can operate with confidence.
Event naming is product design
Bad event names usually signal bad billing design.
These are useful:
api_callsstorage_gbemails_sentcompute_minutes
These are not:
metricusagefeature1event_x
Event codes live in code and in your billing model. If they are vague, every downstream system gets harder to reason about.
Good naming does two things:
- it makes implementation easier for developers
- it makes the billing model easier to audit later

A good ingestion system removes reconciliation work
The point of usage tracking is not just to capture data.
It is to avoid the operational tax of asking:
- did we track the right thing?
- did we track it once?
- does the pricing model still match the product?
- why does the invoice not match what support sees?
When the event contract is explicit, those questions get easier.
That is why Commet keeps the model narrow:
- billable features have event codes
- your app reports consumption against those codes
- Commet aggregates usage for billing
No fake abstraction. No hidden translation layer. No need to rebuild billing logic inside your analytics stack.
The practical standard
If you are building a usage-based product, your ingestion setup should meet a simple standard:
- billable events are named clearly
- retries are idempotent
- batching exists when you need it
- the customer mapping is explicit
- the billing model matches the product behavior
That should be normal infrastructure, not custom billing archaeology.
Read the Track Usage documentation, review Consumption Models, explore the usage-based template, or try Commet in sandbox.