Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A retry is another attempt to deliver or execute work; it is not automatically another billable operation. To prevent inflated tenant usage, define the billable unit, give each logical operation a stable identity, deduplicate usage records at the ledger boundary, and reconcile those records against downstream aggregates. Whether a failed attempt is billable depends on your product’s usage contract.

Why retries can inflate tenant usage

Network clients, queues, and workflow engines retry work for different reasons. A request may time out after the server completed it, a queue may redeliver a message, or a workflow may replay a step. In each case, application code can run more than once even when the customer intended one logical action.

The reverse is also possible: a retry can follow a failure that occurred before the original operation ran. That attempt may be the first execution that actually performs the work. Therefore, counting every attempt as a new billable event can overcharge, while treating every retry as a duplicate can undercount. The usage contract must say what event incurs a charge—for example, a request accepted, a successful operation, or a resource created.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Separate operation identity from attempt identity

Assign an operation ID before work enters any retry boundary, then reuse it on every attempt for that logical action. Keep a separate attempt ID or retry number for diagnostics. The operation ID identifies what may be billed; the attempt ID records how delivery or execution proceeded.

  • Logical operation: the unit defined by the usage contract, such as creating one report.
  • Attempt: one network call, queue delivery, or workflow execution trying to complete that operation.
  • Usage event: the canonical record your billing logic uses to represent the billable operation.

Stripe’s Node.js SDK documentation describes retries for safe failed requests, configurable retry behavior, and a documented maxNetworkRetries default of 1. These are SDK-version-specific details; verify the behavior of the release installed in your service. The SDK also exposes request and response metadata, including request IDs and idempotency keys, which can help trace attempts but should not be confused with your durable usage ledger.

Use idempotency at the right boundary

For outbound API mutations

If an external API supports idempotency keys, send the same key for every attempt of the same logical mutation. Stripe’s API reference states that all POST requests accept idempotency keys and advises against sending them with GET or DELETE, which it describes as idempotent by definition. A provider can then return the recorded result rather than execute the same mutation again.

A provider’s idempotency store is not a permanent usage ledger. Stripe says it may prune keys once they are at least 24 hours old. Retention and behavior are provider-specific, so preserve your own canonical event history for reconciliation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For queue messages and workflow replays

At-least-once execution means code may run again. AWS Durable Execution guidance recommends creating and persisting an idempotency key inside a step so a replay reuses it. Apply the same principle across your own queue consumer: derive or persist the logical event identity before processing, and use it when writing usage.

Build a tenant-safe canonical usage ledger

Store one canonical usage record per billable logical event. A representative record includes:

  • tenant_id or account identity used for billing attribution
  • event_id with a defined uniqueness scope
  • operation_id for tracing the logical action across retries
  • event_time, quantity, and meter or usage dimension
  • Schema or billing-rule version
  • Processing state and, where applicable, correction or audit references

If event IDs are unique only within a tenant, enforce uniqueness on the pair (tenant_id, event_id). If the contract guarantees globally unique IDs, the constraint can reflect that instead. The database constraint must match the actual identity contract; deduplicating on an ID that can collide across tenants risks suppressing one tenant’s valid event.

CREATE TABLE usage_events (
  tenant_id TEXT NOT NULL,
  event_id TEXT NOT NULL,
  operation_id TEXT NOT NULL,
  event_time TIMESTAMPTZ NOT NULL,
  meter TEXT NOT NULL,
  quantity NUMERIC NOT NULL,
  rule_version TEXT NOT NULL,
  PRIMARY KEY (tenant_id, event_id)
);

When a webhook or queue delivery arrives, attempt to insert the canonical event. A uniqueness conflict means that event identity was already recorded; handle it as a duplicate delivery rather than incrementing usage again. Keep delivery-attempt logs separately if they are useful for diagnosing retries. This is an application design pattern, not a claim that an API provider’s idempotency feature replaces your own ledger.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Pass one operation ID through Node.js retries

Create the ID outside the retry loop so a new attempt cannot accidentally create a new billable identity. The example below uses a placeholder API client; adapt its request shape and idempotency mechanism to the service you call.

import { randomUUID } from 'node:crypto';

async function createReport({ tenantId, client, reportInput }) {
  const operationId = randomUUID(); // Create once, before retries.

  const result = await retry(async () => {
    return client.createReport(reportInput, {
      idempotencyKey: operationId,
      tenantId,
    });
  });

  await recordUsageOnce({
    tenantId,
    eventId: operationId,
    operationId,
    eventTime: new Date(),
    meter: 'report_created',
    quantity: 1,
    ruleVersion: 'v1',
  });

  return result;
}

In a production design, decide carefully when to write the usage event relative to the operation’s outcome. If the contract bills successful report creation, emit only after success or use a durable state machine that can recover if the operation succeeds but usage recording fails. If it bills accepted requests, record at acceptance instead. A stable ID makes retries recognizable; it does not decide what the contract bills.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Reconcile canonical events against billed aggregates

Usage aggregates can lag behind event ingestion. Stripe says meter summaries are eventually consistent because usage events are processed asynchronously. A short-lived difference between your ledger and a downstream meter may therefore be processing delay rather than a duplicate or loss.

  1. Choose the same billing rule. Calculate expected usage from canonical events using the contract’s billable-event definition, quantity rules, and billing-period boundary.
  2. Group at the billing dimensions. Compare by tenant, meter or dimension, and billing window—not just by a global total.
  3. Allow for documented processing delay. Do not classify a temporary mismatch as a confirmed error before the downstream aggregation has had time to process events.
  4. Drill into event identities. For each mismatch, inspect tenant attribution, repeated deliveries, late arrivals, corrections, and the rule version used to count the event.
  5. Preserve the audit trail. Avoid silently rewriting already-invoiced events. Record corrections explicitly and rerun reconciliation after accepted late data is processed.

Billing systems need explicit policies for duplicate events, late events, corrections, and changes to counting rules. Stripe’s technical guidance identifies these as design concerns; the appropriate rules depend on your product and customer contract.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose an approach that matches your failure modes

Approach What it protects What it does not guarantee
Stable operation ID passed across retries Connects attempts to one logical action Does not itself prevent duplicate ledger writes
Provider idempotency key Can prevent repeated execution of a supported API mutation within the provider’s rules and retention window Does not automatically deduplicate your webhooks, queue processing, usage events, or tenant rollups
Unique constraint on canonical usage events Prevents a duplicate event identity from being recorded twice within its defined scope Does not correct wrong tenant attribution, an incorrect billable-event rule, or distinct IDs for the same logical action
Event-level reconciliation Makes totals reproducible and helps locate specific mismatches Does not remove legitimate aggregation delay or replace a clear late-event and correction policy

Evaluate the design against identity stability across network, queue, and workflow retries; key scope and retention; tenant isolation; late-event and correction handling; and whether a billed aggregate can be reproduced from canonical event history.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.