Stripe Meter Events That Never Billed: How to Find Them

A usage event Stripe rejects is never metered, so it never reaches an invoice and never reaches QuickBooks. The revenue is missing from both systems at...

Acodei Content Team · 9/21/2026 · 16 min read

Every reconciliation problem in Stripe billing has the same shape. Two systems hold a number, the numbers disagree, and the work is finding out which one is wrong. A payout does not match a deposit. An invoice total does not match a sales receipt. A fee lands in the wrong account. In every case there is something to compare.

Rejected meter events are different, and that is why they survive month-end close. A usage event Stripe refuses to validate is never metered. Because it is never metered, it never reaches an invoice. Because it never reaches an invoice, it is never charged, never paid out, and never synced to QuickBooks. The revenue is missing from Stripe and missing from your books at the same time, in the same amount, for the same reason.

No reconciliation procedure that compares two systems can find a number that is absent from both.

If you bill on usage and you sync Stripe to QuickBooks, this is the one failure your close will not catch. Start a free trial, or see how the Stripe to QuickBooks sync works.

What "invalid" actually means here

Usage billing runs through meters. Stripe's definition is short: a Meter object "specifies how to aggregate meter events over a billing period", meter events "represent all actions that customers take in your system (for example, API requests)", and meters "attach to prices and form the basis of what's billed".

You report usage by sending a meter event carrying the event name configured on the meter, a customer ID, a numerical value and an optional timestamp, as Stripe sets out in recording usage with the API. Stripe then validates it, and validation is where the money goes missing. Stripe's own words for what it produces when validation fails: a Billing Meter Invalid Event object "represents a billing meter event that isn't successfully validated".

Two properties of that process matter more than the definition.

The first is that it is asynchronous. Stripe states that it "processes meter events asynchronously, so aggregated usage in meter event summaries and on upcoming invoices might not immediately reflect recently received meter events". Your application sends the event and moves on. The rejection, when it comes, arrives somewhere your application is not looking.

The second is that a rejection is not an API error. A 429 from the Meter Events endpoint is a rejected request, and your retry logic can see it. An invalid event is a request that succeeded and a usage record that does not exist. Those two failures cost you the same revenue and look completely different in your logs.

The nine ways an event gets thrown out

Stripe publishes the error vocabulary. On the webhook side, reason.error_types.code carries the categorisation that triggered the error, and the possible codes are:

meter_event_customer_not_found, meter_event_no_customer_defined, meter_event_dimension_count_too_high, archived_meter, timestamp_too_far_in_past, timestamp_in_future, meter_event_value_not_found, meter_event_invalid_value, and no_meter, which Stripe notes is "supported only for the v1.billing.meter.no_meter_found event type".

Read as a list it is unremarkable. Grouped by what the operator has to do about it, it splits into four genuinely different problems.

Naming and configuration. no_meter and archived_meter. The event name in your payload does not match a live meter. Stripe's sample error message is "No meter was found matching event_name d2aa8cb3-3f00-44a4-b98f-3fbd1d0e93b1." This is the typo class, and it is the most common way a new integration loses a month. It is also the failure mode with the worst timing, because it usually arrives with a deploy rather than with a customer.

archived_meter deserves its own mention. Archiving a meter is a deliberate administrative act, and it is exactly the sort of act performed during a pricing cleanup by someone who does not own the service still emitting to it. Nothing errors at archive time. The events keep arriving and keep being discarded.

Customer mapping. meter_event_customer_not_found and meter_event_no_customer_defined. Stripe's sample message for the second is "Customer mapping key stripe_customer_id not found in payload." The first is worse in practice: the payload is well formed and names a customer that is not there, which is what a stale internal ID or a wrong-mode ID produces. Usage is attributed to nobody, so nobody is billed for it.

Value. meter_event_value_not_found and meter_event_invalid_value. The event carries no usage number, or one Stripe will not accept. Worth knowing alongside these: Stripe accepts decimal values in the payload, and "if the overall cycle usage is negative, Stripe reports the invoice line item usage quantity as 0". A negative cycle does not produce a credit. It produces a zero.

Timing and volume. timestamp_too_far_in_past, timestamp_in_future, and meter_event_dimension_count_too_high. These are the two traps worth their own sections.

The 35-day window, which is where backfills die

Stripe's rule on timestamps is one sentence: "Make sure the timestamp is within the past 35 calendar days and isn't more than 5 minutes in the future. The 5-minute window is for clock drift between your server and Stripe systems."

Thirty-five days is a deceptively comfortable number. It covers a monthly cycle with a few days to spare, which is why it rarely bites during normal operation and reliably bites during recovery.

Consider the sequence. A meter is misnamed in a deploy on the 3rd. Nobody notices, because nothing errors. It is found during the close for that month, three weeks later, and fixed the same day. The fix is correct, and from that point forward usage meters properly.

Now you go back for the missing usage. Stripe's guidance on remediation is "Correct and resend invalid events for re-processing", which is exactly right and is the only supported answer. But the events you are resending carry their original timestamps, and some of those timestamps are now more than 35 days old. Resending them produces timestamp_too_far_in_past, which is a second invalid event for the same usage.

Two documented rules, neither surprising on its own, combine into a hard deadline: usage that goes unnoticed for more than 35 days cannot be recovered at its original time. You can still bill for it, but only by reporting it at a timestamp inside the window, which prices it under whatever price is in effect now and lands it in a later period. That is a revenue-recognition decision rather than a data fix, and it should be made deliberately with whoever signs off on your revenue, not quietly by a backfill script.

The practical consequence: the interval at which you check for invalid events is not an operational preference. It is the length of your recovery window. Checking monthly leaves almost none.

The cardinality limit, which fails halfway

The dimension limits are the subtlest item on the list, because they do not fail the way limits usually fail.

Stripe caps unique dimension combinations twice over: "For each meter, Stripe accepts up to 10,000 unique combinations for each hour that Stripe receives meter events", and "For each customer on a meter, Stripe accepts up to 100 unique combinations across all events."

Then the behaviour at the cap, quoted in full because the detail is the whole point: "Events for dimension combinations already counted toward the applicable limit continue to process. Events that introduce a new dimension combination after either limit is reached are invalid and appear in v1.billing.meter.error_report_triggered with the meter_event_dimension_count_too_high error code."

So the meter does not stop. Established combinations keep billing normally while new ones are discarded. Your invoices still arrive, still look plausible, and are short by exactly the usage belonging to whatever was new this hour. New dimension values tend to correlate with new activity, which means the usage most likely to be dropped is the usage from your newest customers or newest features.

That is a partial failure wearing the costume of a working system, and it is invisible to any check that asks whether invoices were generated.

Detection, part one: the two events Stripe already sends you

Stripe emits two events when it finds problems, and most accounts are not listening to either:

EventStripe's description
v1.billing.meter.error_report_triggered"This event occurs when a meter has invalid usage events."
v1.billing.meter.no_meter_found"This event occurs when usage events have missing or invalid meter IDs."

Both use the thin payload type, and there is a setup step that catches people: Stripe notes that "to create an event destination that subscribes to thin events, enable Workbench in your Developer settings". A thin event carries a summary rather than the full object. The payload includes a developer_message_summary ("There is 1 invalid event" in Stripe's example), an error_count, the error_types array with sample errors, and validation_start and validation_end timestamps bounding the window the report covers. The related object on an error report is the meter itself.

Two operational notes worth building into the handler. First, the sample errors are samples, not the full set, so treat error_count as the number that matters and the messages as diagnosis. Second, these are the events to alert a human on rather than to log. An error report that lands in a dashboard nobody opens is the same as no error report, and the cost of missing it compounds daily against the 35-day window.

If your webhook handling already has gaps, they will show up here too. We have written separately about Stripe webhook events that never reach QuickBooks, and the same delivery and signature problems apply to these two event types.

Detection, part two: the query that lists them

Webhooks tell you about problems going forward. To ask what already happened, the data is in Sigma.

Stripe exposes invalid events through the billing_meter_invalid_events table, documented alongside the rest of the billing schema in querying billing data, with a companion table that "contains the event payload from the original event" as billing_meter_invalid_events_payload. The join is the useful part, because the invalid event itself does not carry a customer column. The customer is in the payload, as a key-value pair.

Stripe's own example, which returns all invalid billing meter events for a specific customer:

SELECT
  billing_meter_invalid_events.id as event_id,
  billing_meter_invalid_events.error_code,
  billing_meter_invalid_events.error_message
FROM
  billing_meter_invalid_events
  JOIN billing_meter_invalid_events_payload ON billing_meter_invalid_events_payload.event_id = billing_meter_invalid_events.id
WHERE
  billing_meter_invalid_events_payload.key = 'stripe_customer_id'
  AND billing_meter_invalid_events_payload.value = 'cus_EDQkYj7P2Jf3sJ1'

Stripe's sample output returns rows with an error_code of METER_NOT_FOUND and an error_message of "No meter found matching event_name mtr_orWziM4j7CiRL8J."

One detail to carry into your own queries. The error_code value shown in Stripe's Sigma sample is upper-case METER_NOT_FOUND, while the webhook reason.error_types.code vocabulary listed earlier is lower-case and uses no_meter for the same situation. These are two different surfaces documented in two different places, so match against the value your own rows actually contain rather than assuming the two sets are spelled identically.

For a monthly check you want the inverse of Stripe's example: drop the customer filter and group by error_code to see the shape of the problem, then join the payload back in to find out who it happened to. If you are new to Sigma, our month-end close queries start from balance transactions and cover the same tooling.

The cross-check that does not need Sigma

There is a lighter test if you do not have Sigma or do not want a scheduled query.

Stripe exposes aggregated usage through billing_meter_event_summaries, which "represents an aggregated view of a customer's billing meter events within a specified timeframe" and "how much usage a customer accrues for that period". Hourly granularity is available, indicated by the value_grouping_window column.

The check is to compare, for one customer and one period, three numbers: what your own application believes it sent, what the meter summary aggregated, and what the invoice line item actually billed.

Those three numbers answer different questions, and the gap tells you where the problem is:

  • Sent higher than aggregated. Events were rejected. Go to the invalid events.
  • Aggregated higher than billed. Everything metered, and something between the meter and the invoice dropped it. That is usually billing mode, which we cover in Stripe usage billing when the price changes mid-period.
  • All three agree. The month is clean on this meter.

Only the first of those three is the failure this post is about, and it is the only one that no downstream check can find. That is the argument for running this comparison on a schedule rather than after a customer complains.

What reaches QuickBooks, and why nothing looks wrong

Nothing reaches QuickBooks. That is the entire accounting story, and it is worth being precise about why.

Acodei's sync is driven by Stripe webhook events. Stripe emits an event, Acodei turns each relevant one into a transaction record, and a queue of jobs writes the corresponding QuickBooks record: charges become sales receipts or payments, invoices become invoices, credit notes become credit memos, payouts become transfers or deposits. Every one of those paths begins with a Stripe object that exists.

An invalid meter event never becomes any of them. It does not produce a charge, an invoice, or a payout, because Stripe discarded it before it could contribute to a meter total. There is no failed sync, no error in the data feed, and no transaction in a failed state, because nothing was ever handed over.

The same point holds one level up. As we noted when covering usage billing and price changes, Acodei's product documentation covers no reading of meters, usage records or unbilled usage, and it does not need to: by the time an invoice is finalized, Stripe has already settled which usage is on it and at what price. Everything in this post happens upstream of that point.

So the QuickBooks side is not merely unhelpful here, it is structurally incapable of helping, and so is every other tool that reads Stripe and writes to a ledger. A sync that faithfully copies a Stripe invoice will faithfully copy an invoice that is short. The copy is correct. The source was wrong before anyone copied it.

This is worth saying plainly because the instinct when revenue looks low is to audit the sync. The sync is the last place this failure could possibly be. The first place is the meter.

What to do about it

Five things, in the order they pay off.

  1. Subscribe to both error events. v1.billing.meter.error_report_triggered and v1.billing.meter.no_meter_found, with the thin payload style, and enable Workbench first. Route them to a human, not to a log.
  2. Check invalid events weekly, not monthly. The 35-day timestamp window means a monthly cadence can leave you with no recovery time at all. Weekly leaves three weeks of room.
  3. Compare sent, aggregated and billed for one customer each month. One customer, one meter, one period. It is the only check that tests the whole path rather than your belief about it.
  4. Alert on silence as well as on errors. A meter that suddenly reports nothing looks identical to a customer who stopped using the product. Expected-volume alerting per meter catches the misnamed deploy on day one instead of at close.
  5. Decide the policy for unrecoverable usage before you need it. If usage falls outside the 35-day window, billing it means reporting it at a current timestamp under a current price in a later period. Agree with your accountant whether you do that, write it down, and do not leave it to whoever runs the backfill.

Frequently asked questions

What happens to a Stripe meter event that fails validation?

It is not metered. Stripe creates a Billing Meter Invalid Event object, which "represents a billing meter event that isn't successfully validated", and the usage never contributes to a meter total. Because it never reaches a meter total, it never reaches an invoice, is never charged, and never syncs to your accounting system. The revenue is absent from Stripe and from your books simultaneously.

How do I find invalid meter events in Stripe?

Two ways. Subscribe to the v1.billing.meter.error_report_triggered and v1.billing.meter.no_meter_found events for ongoing notification, using the thin payload style with Workbench enabled. For historical analysis, query the billing_meter_invalid_events table in Sigma, joined to billing_meter_invalid_events_payload on event_id to recover the customer from the payload.

How far back can I resend a Stripe meter event?

Stripe requires the timestamp to be within the past 35 calendar days, and not more than 5 minutes in the future. Resending a corrected event that carries an original timestamp older than 35 days produces a timestamp_too_far_in_past error. Usage discovered after that window can only be billed by reporting it at a current timestamp, which prices it at the current price and places it in a later period.

Why is my Stripe usage invoice lower than my application's usage logs?

The most likely causes are rejected meter events, where usage never reached the meter at all, or classic billing mode dropping usage reported before a mid-period price change, where usage reached the meter but not the invoice. Comparing what you sent, what the meter event summary aggregated and what the invoice line billed tells you which of the two you have.

Does a rejected meter event show up as a sync error in QuickBooks?

No. A rejected event produces no charge, no invoice and no payout, so there is nothing for a sync to act on and nothing to fail. The absence is silent on both sides, which is what makes a periodic meter check necessary rather than optional.

Can hitting Stripe's dimension limits cause partial usage loss?

Yes, and this is the failure mode that looks most like a working system. Stripe accepts up to 10,000 unique dimension combinations per meter per hour and 100 per customer per meter. Events for combinations already counted continue to process, while events introducing a new combination after the limit is reached are invalid. Established usage keeps billing while new usage is discarded.

Is a 429 rate limit response the same as an invalid event?

No, and the difference matters for your retry logic. The Meter Event endpoint has its own live mode limit of 1,000 calls per second per account, with one concurrent call per customer per meter, and Connect platforms making requests on a connected account with the Stripe-Account header are subject to the regular limit of 100 operations per second. A 429 is a request your code can see and retry. An invalid event is a request that succeeded and a usage record that does not exist.

The short version

Usage billing has one failure that leaves no trace anywhere a reconciliation looks. A rejected meter event is not a discrepancy between two systems. It is an absence from both, and the only place it is visible is in Stripe's invalid event data, which nothing reads unless you tell it to.

The fix is small and it is entirely preventive: subscribe to two webhook events, run one query on a weekly cadence, and compare one customer's usage end to end each month. What makes it urgent rather than tidy is the 35-day timestamp window, which converts "we will look into it after close" into usage that can no longer be billed in the period it belonged to.

Once the invoice is right, getting it into QuickBooks is the easy part. Acodei creates a QuickBooks invoice from the finalized Stripe invoice, reproducing every line item and the tax lines as your mapping settings allow. Start a free trial, or read more about how the Stripe to QuickBooks sync works.

Share

Automate your Stripe to QuickBooks sync

Save hours every month. Acodei automatically syncs your Stripe transactions, invoices, and payouts to QuickBooks Online.

How Acodei handles this in your stack

Stripe QuickBooks Integration

See how Acodei syncs Stripe payments, fees, refunds, invoices, and payouts into QuickBooks Online automatically.

Or go straight to a capability

Get more operational finance guides like this one

We will only send high-value product and finance content.