Subscription Checkouts
A subscription checkout is a single-use intent that lets a customer authorize a recurring stablecoin subscription. It mirrors the one-shot Checkout flow: the merchant creates an intent on the server, redirects the customer to a hosted subscribe page, and receives webhooks when the customer signs and the first charge confirms on-chain.
Why intents
The subscription plan (price, feature gating, billing day) lives on the merchant side. Exodus only records the per-subscriber intent and captures the resulting on-chain state. One intent maps to one subscriber and one resulting subscription — same lifecycle as one-shot Checkout.
This means there are no reusable /subscribe/:planId URLs in v1: every subscribe flow starts with a fresh merchant-server call to POST /subscription-checkouts.
Atomic first charge
When the customer signs at the hosted page, the on-chain subscribeAndCharge transaction registers the subscription and performs the first charge in a single transaction. The intent never transitions to completed without a confirmed first charge — there is no “subscribed but not charged” state.
Lifecycle
| Status | Meaning |
|---|---|
pending | Intent created; no on-chain subscribe yet. |
completed | Customer signed subscribeAndCharge; first charge confirmed; Subscription materialized. |
cancelled | Merchant called PATCH /subscription-checkouts/:id/cancel, or customer dismissed the hosted page. |
expired | expires_at elapsed without a successful subscribe. Default expiry is 5 minutes; maximum is 24 hours. |
Race resolution — on-chain truth wins
If a subscribeAndCharge transaction is already in the mempool when the merchant cancels (or when the cron sweeper marks the intent expired), and that transaction later confirms on-chain, the indexer flips the intent back to completed and dispatches a subscription_checkout.completed webhook.
Merchants must handle late subscription_checkout.completed webhooks that arrive after a
.cancelled or .expired event. The latest webhook is authoritative.
Chain identifiers (CAIP-2)
The subscription surface uses CAIP-2 chain identifier strings throughout — eip155:1 for Ethereum mainnet, eip155:137 for Polygon, etc. The merchant publishes supported_chains: string[] at intent creation; the customer picks one at sign time based on their wallet balances.
V1 supports the eip155: namespace only (EVM chains).
| CAIP-2 | Network |
|---|---|
eip155:1 | Ethereum mainnet |
eip155:137 | Polygon |
eip155:42161 | Arbitrum One |
eip155:8453 | Base |
eip155:10 | Optimism |
Amounts, budget, and cap
The merchant prices the plan in a fiat currency at intent creation. A crypto-settled business may use any currency its FX source can quote; a fiat-settled business must use its own settlement currency. The subscriber picks the settlement stablecoin (and chain) at subscribe time — never the merchant, and never at creation.
Every fiat amount is an integer in hundredths of the currency unit. price, cap, and budget all follow this rule, so "150000" is 1,500.00 ARS and "9999" is 99.99 ARS. Send them as strings, with no decimal point and no thousands separator. Every currency the API accepts today has two decimal places, so the conversion is always the amount multiplied by 100.
| Field | Meaning |
|---|---|
price | Required. The recurring per-cycle amount, in price_currency minor units (e.g. "150000" = 1,500.00 ARS). |
price_currency | Required. ISO-4217 currency code, which must be quotable by the business’s FX source. For a crypto-settled business that source is the Exodus pricing server, which quotes ARS only today; anything else is rejected with 422 price_currency_not_quotable. A fiat-settled business quotes through its settlement provider instead, so it must have a complete fiat settlement configuration (422 fiat_settlement_required) and must price in its own settlement currency (422 currency_mismatch) — the quotable set there is whatever that provider supports, currently ARS. |
budget | Optional. The per-cycle spend ceiling — the maximum total that can be charged within a single billing window (supports multiple metered charges per cycle), in price_currency minor units. Defaults to 10x price when omitted. |
cap | Optional. Per-charge maximum — the most a single charge can pull, in price_currency minor units. Defaults to budget (provided or derived) when omitted. |
period_duration | Seconds between scheduled charges. Minimum 1 hour. Day-of-month and other calendar billing is the merchant’s responsibility — schedule the API call when desired. |
price, cap, and budget all share price_currency’s minor units, so the ordering always holds: price <= cap <= budget. budget bounds the total pulled per billing window; cap bounds any single charge. At subscribe time, the quote converts all three to the settlement token’s smallest units at one rate, rounding up — the subscriber signs those converted amounts, not the fiat figures directly. A cycle whose charges would exceed the converted budget, or a single charge above the converted cap, reverts on-chain — never a silent over-pull. Headroom above price accommodates later price increases without re-subscribing.
subscription_options enumerates every stablecoin configured on every supported chain the subscriber can pay with — always iterate it as an array, never assume a single entry. It’s present only on this direct API response (POST/GET /subscription-checkouts), not on any subscription_checkout.* webhook — by the time a webhook fires, the subscriber has already chosen, and that choice shows up on data.subscription instead. See List subscription checkouts for an example payload.
The customer’s wallet approves the on-chain contract for uint256.max allowance at subscribe time
(industry standard pattern matching Uniswap, Aave, etc.). budget and cap enforcement happens in
the contract, not the ERC-20 allowance.
Hosted pages
Two hosted pages live on checkout.exodus-int.com:
https://checkout.exodus-int.com/subscribe/:subscription_checkout_id— the subscribe page returned incheckout_url. The customer connects a wallet, picks a chain, optionally signs an EIP-2612 permit, and signssubscribeAndCharge.https://checkout.exodus-int.com/cancel/:subscription_id— a customer-facing cancel page keyed on the on-chainsubscription_id. The customer connects the subscribing wallet and signscancel(subscriptionId)directly. Surface this URL in your customer account UI for self-service cancellation.
Both pages pick up your branding colors automatically.
Available Endpoints
- Create Subscription Checkout — Create a new intent and redirect URL
- Get Subscription Checkout — Retrieve intent details
- List Subscription Checkouts — List intents with filters
- Cancel Subscription Checkout — Cancel a pending intent
After the customer signs, the resulting on-chain subscription is managed via the Subscriptions endpoints.
