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:
- Call
POST /subscription-checkoutsto create an intent. - Redirect the customer to the returned
checkout_url. - The customer signs
subscribeAndChargefrom their wallet. - The indexer materializes the
Subscriptionand dispatches thesubscription.createdandsubscription.chargedwebhooks.
Pricing
A plan is priced either directly in the settlement token or in a FIAT currency, set on the originating subscription checkout via price + price_currency:
- Token-denominated (
price_currencyomitted) —priceis in the settlement token’s smallest units; every cycle charges exactlyprice. - FIAT-denominated (
price_currencyan ISO 4217 code, e.g.ARS) —priceis in the currency’s minor units. Each cycle re-rates to the settlement token at the live rate, so the subscriber pays the current FIAT-equivalent amount, bounded bycap_amount.
The per-cycle amount is not stored on the subscription. Before each charge the merchant fetches the current amount from POST /subscriptions/:id/charge-quote, signs it, and submits.
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.
| Action | Signer helper | Endpoint |
|---|---|---|
| Charge a subscriber for a cycle | signCharge | POST /subscriptions/:id/charge |
| Cancel a subscription | signCancelSubscription | POST /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.
Plan upgrades and downgrades (raising the cap, with optional proration) are subscriber-authorized — the subscriber consents on the hosted page (modifySubscription). A merchant key can only charge within the subscriber’s cap and cancel; it can never raise the cap or pull off-cycle.
Customer-Initiated Cancel
Customers cancel their own subscriptions from a hosted page:
https://checkout.exodus-int.com/cancel/:subscription_idThe 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
| Status | Description |
|---|---|
active | Subscription is live. Eligible for charge once next_charge_at has elapsed. |
cancelling | Cancel transaction has been submitted on-chain and confirmed. No further charges are eligible. Transitions to cancelled once the indexer ingests the SubscriptionCancelled event. |
cancelled | Subscription cancelled on-chain. No further charges possible. |
Paused (in-band sub-state of active)
Subscriptions also carry a separate paused: boolean field — an in-band brake on the active state, not a status enum value. The Exodus scheduler skips charges while paused is true.
| Trigger | Effect |
|---|---|
charge reverts pre-flight with InsufficientBalance or InsufficientAllowance (subscriber fault) | paused = true |
charge receipt reverts on-chain (any code) | paused = true |
Next successful charge confirms on-chain | paused = false |
paused is visible on every GET /subscriptions/:id and GET /subscriptions response. Surface it in your dunning UI alongside the failed-charge ledger.
Cap Semantics
Every subscription has a cap_amount — the on-chain maximum the contract will allow per call. It is set by the customer when they subscribe and cannot be raised by the merchant — only the subscriber can change it, via the hosted upgrade flow (modifySubscription) or a standalone cap change.
chargeenforcesamount <= cap_amountper call. A cycle whose amount — or, for FIAT plans, the re-rated settlement-token amount — exceeds the cap reverts withChargeAmountExceedsCapuntil the subscriber raises the cap.- The cap is the subscriber’s security dial: a price increase past it reverts charges rather than silently pulling more.
A merchant who wants headroom for future price increases should pass a generous recommended_cap (a multiple of price) at intent creation — the customer sets the actual cap_amount when they subscribe, seeded by that suggestion.
Failed Charges
The Exodus API persists every charge that reached the chain — succeeded or receipt-reverted — in the Subscription Charges ledger. Pre-flight reverts (the contract rejected the simulation before submission) return a 4xx response and are not persisted. Common typed contract errors:
failure_reason | Cause |
|---|---|
InsufficientBalance | Customer’s wallet balance is below amount. |
InsufficientAllowance | Customer revoked the ERC-20 allowance to the SubscriptionManager. |
ChargeAmountExceedsCap | Attempted to charge more than cap_amount. |
PeriodNotElapsed | Tried to call charge before next_charge_at elapsed. |
SubscriptionNotActive | Subscription is no longer active on-chain. |
Only succeeded charges fire the subscription.charged webhook. Failed attempts that reached the chain are visible via GET /merchants/:id/charges with status=failed — poll this endpoint for dunning and analytics rather than relying on a webhook for failures.
Available Endpoints
- Get Subscription — Retrieve subscription details
- List Subscriptions — List all subscriptions
- Charge a Subscription — Quote the cycle amount, sign it, and submit the charge
- Cancel Subscription — Cancel a subscription as the merchant
See also: Subscription Checkouts for the intent that creates a subscription, and Subscription Charges for the per-charge ledger.
