Skip to Content

List Subscription Charges

GET/merchants/:merchantId/charges

Description

List all subscription charges for the merchant — both succeeded and failed. Results are returned in descending order by a monotonic internal sequence, so late-arriving rows always land at the head and never fall into already-paged regions.

Use this endpoint for:

  • Per-subscription health dashboards (filter by subscription_id)
  • Dunning workflows (filter by status=failed)
  • CSV exports for reconciliation (from / to date range)
  • Per-customer activity views (filter by subscriber or external_customer_id)

Headers

HeaderDescriptionRequired
AuthorizationBearer token with your API keyyes

Path Parameters

NameTypeDescriptionRequired
merchantIdstringYour merchant ID (visible in the dashboard).yes

Query Parameters

NameTypeDescriptionRequired
limitnumberMaximum number of charges to return (default: 50, max: 100).no
starting_afterstringCursor for pagination. Returns charges after this charge ID (`subc_...`).no
ending_beforestringCursor for pagination. Returns charges before this charge ID.no
statusstringFilter by status: `succeeded` or `failed`.no
kindstringFilter by kind: `cycle` or `adhoc`.no
subscription_idstringFilter by parent subscription.no
subscriberstringFilter by on-chain subscriber address.no
external_customer_idstringFilter by the merchant-supplied customer reference (matched on the parent subscription).no
chainstringCAIP-2 chain identifier (e.g. `eip155:1`).no
fromstringISO 8601 timestamp. Inclusive lower bound on `charged_at` (block timestamp on success, API submission time on failure).no
tostringISO 8601 timestamp. Inclusive upper bound on `charged_at`.no

Example Request

const response = await fetch(
  'https://checkout.exodus.com/merchants/mer_42/charges?limit=20&status=failed',
  {
    headers: {
      Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx',
    },
  },
);

Response

SUCCESSFUL RESPONSE
{
  "object": "list",
  "data": [
    {
      "object": "subscription_charge",
      "id": "subc_7890abcdef123456",
      "subscription_id": "sub_abc123def456",
      "subscriber": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
      "amount": "9990000",
      "fee": null,
      "tx_hash": "0xdef...",
      "chain": "eip155:1",
      "charge_nonce": 4,
      "charged_at": "2026-06-19T12:00:00Z",
      "status": "failed",
      "kind": "cycle",
      "failure_reason": "InsufficientBalance"
    },
    {
      "object": "subscription_charge",
      "id": "subc_6789abcdef012345",
      "subscription_id": "sub_abc123def456",
      "subscriber": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
      "amount": "9990000",
      "fee": "0",
      "tx_hash": "0xabc...",
      "chain": "eip155:1",
      "charge_nonce": 3,
      "charged_at": "2026-05-19T12:02:18Z",
      "status": "succeeded",
      "kind": "cycle",
      "failure_reason": null
    }
  ],
  "has_more": true
}

Pagination Guarantees

The cursor is strictly monotonic in ingestion order, not on-chain time. This means:

  • A row written by the indexer’s late-backfill path (a merchant who submits charges directly from their own RPC, captured by Alchemy webhook minutes later) always lands at the head of the list.
  • Resuming pagination days later never loses rows from already-paged regions.
  • The from / to range filter targets charged_at (block timestamp), not ingestion time, so date-range exports remain stable.

Merchants should still deduplicate by id when reconciling — Alchemy webhook redeliveries are swallowed by tx_hash uniqueness, but late-arriving rows whose charged_at falls inside an already-queried date range will re-appear at the head on subsequent queries.

next page
const nextPage = await fetch(
  'https://checkout.exodus.com/merchants/mer_42/charges?limit=20&starting_after=subc_6789abcdef012345',
  {
    headers: {
      Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx',
    },
  },
);

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