Skip to Content
CheckoutAPI ReferenceSubscriptionsOverview

Subscriptions

A Subscription is the on-chain record of an active recurring stablecoin agreement. It is materialized by the indexer when a customer completes a subscription checkout and the first subscribeAndCharge transaction confirms on-chain.

Creation

Subscriptions are not created via a direct API call. To start one:

  1. Call POST /subscription-checkouts to create an intent.
  2. Redirect the customer to the returned checkout_url.
  3. The customer signs subscribeAndCharge from their wallet.
  4. The indexer materializes the Subscription and dispatches the subscription.created and subscription.charged webhooks.

Merchant-Driven Actions

After creation, the merchant drives all subsequent state changes via the API. Each action is a signed request: the merchant uses @exodus/checkout-signer to produce a signature, posts it to the Exodus API, and Exodus submits the on-chain transaction.

ActionSigner helperEndpoint
Charge a subscriber for a scheduled cyclesignChargePOST /subscriptions/:id/charge
Charge an off-cycle one-shot amountsignChargeAdHocPOST /subscriptions/:id/charge-adhoc
Change the recurring pricesignUpdateChargeAmountPOST /subscriptions/:id/update-charge-amount
Cancel a subscriptionsignCancelSubscriptionPOST /subscriptions/:id/cancel
🔐

All merchant actions require a signing key. See Signed Requests for setup, and the per-action pages for the exact signer call and request body.

Customer-Initiated Cancel

Customers cancel their own subscriptions from a hosted page:

https://checkout.exodus.com/cancel/:subscription_id

The page reads the on-chain subscription_id, requires the customer’s subscribing wallet to be connected, and lets them sign cancel(subscriptionId) directly. Surface this URL in your customer account UI for self-service cancellation. The customer pays the on-chain gas.

If the customer has lost access to their subscribing wallet, the page surfaces a “Lost wallet?” link. Exodus does not perform key recovery — recovery routes back to the merchant via the intent’s cancel_url, and the merchant cancels via POST /subscriptions/:id/cancel.

Statuses

StatusDescription
activeSubscription is live. Eligible for charge once next_charge_at has elapsed; eligible for charge_adhoc at any time.
cancellingOff-chain honor flag set via cancel_at_period_end on a cancel call. Contract still allows charges; the merchant scheduler skips them. Transitions to cancelled when the on-chain cancelWithSig is submitted (or when the customer cancels directly).
cancelledSubscription cancelled on-chain. No further charges possible.

cancelling is honor-system off-chain. The on-chain contract has no concept of “scheduled to cancel” — it only knows active and cancelled. The Exodus scheduler honors the flag and skips scheduled charges; merchants self-submitting from their own RPC must honor it themselves.

Cap Semantics

Every subscription has a cap_amount — the on-chain maximum the contract will allow per call. It is set once when the customer subscribes and cannot be raised by the merchant (the customer would have to re-sign a new subscription).

  • charge enforces amount <= cap_amount per call.
  • charge_adhoc enforces amount <= cap_amount per call (no cumulative tracking).
  • update_charge_amount enforces new_amount <= cap_amount. Reverts with ChargeAmountExceedsCap otherwise.

A merchant who wants headroom for future price increases should set cap_amount to a multiple of charge_amount at intent creation.

Failed Charges

The Exodus API submits charges and persists the outcome of every attempt — both succeeded and failed — in the Subscription Charges ledger. Common typed contract errors:

failure_reasonCause
InsufficientBalanceCustomer’s wallet balance is below amount.
InsufficientAllowanceCustomer revoked the ERC-20 allowance to the SubscriptionManager.
ChargeAmountExceedsCapAttempted to charge more than cap_amount.
PeriodNotElapsedTried to call charge before next_charge_at elapsed.
SubscriptionNotFoundSubscription does not exist on the contract.
SubscriptionAlreadyCancelledTried to charge a cancelled subscription.

Failed scheduled charges fire the subscription.charged webhook with data.object.status: "failed" and the typed failure_reason. Use the GET /merchants/:id/charges endpoint with status=failed for analytics and dunning workflows.

Available Endpoints

See also: Subscription Checkouts for the intent that creates a subscription, and Subscription Charges for the per-charge ledger.

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