Skip to Content
CheckoutAPI ReferenceSubscription ChargesOverview

Subscription Charges

A SubscriptionCharge is the per-attempt record of a charge against a Subscription. Both succeeded and failed charges are persisted — failed attempts include a typed failure_reason for dunning, analytics, and CSV exports.

What’s in a Charge

FieldDescription
idsubc_... resource id, stable across redeliveries.
subscription_idThe parent subscription.
subscriberOn-chain subscriber address (denormalized for filtering).
amountCharged amount in token’s smallest unit. For failed charges this is the attempted amount.
feeNetwork fee deducted, in token’s smallest unit. null on failed charges that did not settle on-chain.
tx_hashOn-chain transaction hash. null only when the transaction was never broadcast (rare — preflight rejections are NOT persisted here).
chainCAIP-2 identifier (e.g. eip155:1).
charge_nonceMonotonic per-subscription counter. 0 for the first charge from subscribeAndCharge. Shared between cycle and ad-hoc charges.
charged_atBlock timestamp on success; API submission time on failure.
statussucceeded or failed.
kindcycle (from POST /subscriptions/:id/charge) or adhoc (from POST /subscriptions/:id/charge-adhoc).
failure_reasonTyped contract error name on failure (e.g. InsufficientBalance, ChargeAmountExceedsCap, PeriodNotElapsed). null on success.

Sources of Truth

The same charge can be written by two paths:

  1. API write path — when a merchant calls POST /subscriptions/:id/charge or /charge-adhoc, Exodus submits the transaction and persists the receipt synchronously.
  2. Indexer backup path — when a merchant submits directly from their own RPC, the indexer ingests the SubscriptionCharged / SubscriptionChargedAdHoc event and inserts a row.

Rows are deduplicated by tx_hash (unique). Cursor pagination (see List Subscription Charges) uses an internal monotonic sequence, so even if a row arrives via the indexer’s late backfill it lands at the head of the list without disturbing already-paged rows.

Read-Your-Writes

After receiving a subscription.charged webhook, the corresponding row is durable in the ledger. An immediate GET /merchants/:id/charges with no cursor — or with starting_after pointing to a row strictly older than the new charge — is guaranteed to include it.

API-side preflight rejections are NOT persisted as charges. When the API rejects a call with period_not_elapsed, charge_amount_exceeds_cap, invalid_signature, nonce_mismatch, or subscription_cancelled, no on-chain transaction is submitted and no SubscriptionCharge row is written. Track these in your scheduler logs.

Available Endpoints

Start building

XO

Request Demo

Schedule a call with our team

Select a product
Arrow right

Start building
Grateful

Contact Us

We're here to help