Quickstart
Start accepting recurring stablecoin payments in just a few minutes.
Prerequisites
Before you begin, you’ll need:
- An Exodus Business account - Contact [email protected] to create your account and complete the onboarding process
- API credentials - Once your account is approved, you’ll find your API keys in your dashboard settings
- Settlement address - Configure the wallet address where you’ll receive stablecoin payments
- Webhook secret - Configure your webhook endpoint URL in the dashboard to receive your webhook signing secret
- A server-side environment - Node.js, Python, Ruby, or any language that can make HTTP requests
New to Exodus Business? Our team will guide you through the onboarding process, including KYB verification and account configuration. Reach out to [email protected] to get started.
Never expose your API key in client-side code. Always make API calls from your server.
Set Up Your Signing Key
Install the signing SDK and generate a key pair:
npm install @exodus/checkout-signerimport { generateMnemonicSigningKeys } from '@exodus/checkout-signer'
const { mnemonic, evm, solana } = generateMnemonicSigningKeys()
console.log('Mnemonic:', mnemonic) // store securely as SIGNING_MNEMONIC
console.log('EVM signer address:', evm.address) // send to your account manager
console.log('Solana signer address:', solana.address) // send to your account managerStore the mnemonic in your environment variables, and send the evm.address and solana.address to your account manager. You can also generate the mnemonic from your Exodus dashboard.
Create a Direct Payment
This is the simplest payment flow. Funds go directly to your settlement address when the customer pays.
import { signDirectPaymentMultiChain } from '@exodus/checkout-signer'
// Your PaymentFactory address, from GET /settings
const factoryAddress = '0xfaceb00cfaceb00cfaceb00cfaceb00cfaceb00c'
const signatures = signDirectPaymentMultiChain(process.env.SIGNING_MNEMONIC, {
factoryAddresses: { evm: factoryAddress },
})
const response = await fetch('https://checkout-api.exodus-int.com/checkouts', {
method: 'POST',
headers: {
Authorization: 'Bearer sk_test_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 2999,
currency: 'USD',
payment_method: 'direct',
signatures: {
evm: {
on_chain_id: signatures.evm.onChainId,
signature: signatures.evm.signature,
},
},
description: 'Pro Plan - Monthly',
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel',
}),
})
const checkout = await response.json()
console.log(checkout.checkout_url)Create Your First Subscription
Subscriptions in v1 start with a single-use Subscription Checkout intent. The customer signs an on-chain authorization on the hosted page, and the first charge is atomic with the subscribe. There is no separate “activate” step.
const response = await fetch('https://checkout-api.exodus-int.com/subscription-checkouts', {
method: 'POST',
headers: {
Authorization: 'Bearer sk_test_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
token_symbol: 'USDC', // settlement token the subscriber pays in
price: '9990000', // $9.99 USDC (6 decimals); omit price_currency = token-denominated
recommended_cap: '120000000', // suggested cap (the subscriber sets the on-chain cap when signing)
period_duration: 2592000, // 30 days in seconds
supported_chains: ['eip155:1', 'eip155:137'], // CAIP-2 ids the subscriber picks from at sign time
external_customer_id: 'cus_42',
success_url: 'https://yoursite.com/welcome',
cancel_url: 'https://yoursite.com/pricing',
metadata: { external_plan_ref: 'pro_monthly' },
}),
})
const intent = await response.json()
// Redirect your customer to intent.hosted_url
console.log(intent.hosted_url) // https://checkout.exodus-int.com/subscribe/schk_...cap_amount is the on-chain ceiling, set by the subscriber. They authorize the contract for
any amount up to cap_amount, so each cycle charge can vary (e.g. a FIAT-priced plan re-rated to
USDC) as long as it stays under the cap. Pass a generous recommended_cap up front to seed it;
raising the cap later is a subscriber-authorized action on the hosted page.
Redirect the Customer
Send your customer to the hosted_url returned in the response:
// After receiving the hosted URL from your server
window.location.href = hostedUrlThe customer will:
- Connect their wallet (Exodus, Grateful, MetaMask, Phantom, etc.)
- Pick the chain to subscribe on (out of
supported_chains) - Sign the on-chain
subscribeAndCharge. The first charge clears in the same transaction - Be redirected to your
success_urlonce confirmed
Handle Webhooks
Set up a webhook endpoint to receive subscription events:
import express from 'express'
import crypto from 'crypto'
const app = express()
// Use express.raw() so we can verify the signature over the unparsed body
app.post('/webhooks/payments', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature']
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body)
.digest('hex')
if (signature !== expectedSignature) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body)
switch (event.type) {
case 'subscription_checkout.completed': {
// Customer subscribed AND the first charge cleared on-chain.
// Payload bundles the intent, the subscription, and the first charge.
const { object: intent, subscription, first_charge } = event.data
console.log('Subscription started:', subscription.id)
// Grant access, keyed on intent.external_customer_id ("cus_42")
break
}
case 'subscription.charged': {
const charge = event.data.object
if (charge.status === 'succeeded') {
console.log('Cycle charge succeeded:', charge.id)
} else {
// On a failed attempt, `failure_reason` is a typed contract error
// (e.g., "InsufficientBalance"). Drive your dunning flow from here.
console.log('Cycle charge failed:', charge.id, charge.failure_reason)
}
break
}
case 'subscription.cancelled': {
console.log('Subscription cancelled:', event.data.object.id)
// Revoke access
break
}
}
res.status(200).send('OK')
})
app.listen(3000)Charging Subsequent Cycles
Subscriptions don’t auto-charge on a schedule. Your scheduler decides when to call the API. For each cycle, sign a charge with your private key and POST it:
import { signCharge } from '@exodus/checkout-signer/evm'
import { signingKeysFromMnemonic } from '@exodus/checkout-signer'
const headers = { Authorization: `Bearer ${process.env.API_KEY}` }
const { privateKey } = signingKeysFromMnemonic(process.env.SIGNING_MNEMONIC).evm
// Read the latest nonce & on-chain ids from the subscription
const sub = await fetch(`https://checkout-api.exodus-int.com/subscriptions/${subscriptionId}`, {
headers,
}).then((r) => r.json())
// Quote the per-cycle amount (fixed price for token plans, a live FX quote for FIAT plans)
const quote = await fetch(`https://checkout-api.exodus-int.com/subscriptions/${sub.id}/charge-quote`, {
method: 'POST',
headers,
}).then((r) => r.json())
const signature = signCharge(
sub.id,
quote.amount,
sub.charge_nonce,
sub.subscription_manager_address,
sub.chain,
privateKey,
)
await fetch(`https://checkout-api.exodus-int.com/subscriptions/${sub.id}/charge`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json', 'X-Signature': signature },
body: JSON.stringify({ amount: quote.amount }),
})Listen for the subscription.charged webhook to learn the on-chain outcome. Both succeeded and failed attempts fire it.
Test Your Integration
Use test mode to verify your integration works:
- Use your test API key (
sk_test_...) - Create a test subscription checkout
- Complete the subscribe flow using a testnet wallet
- Verify your webhook receives
subscription_checkout.completed
Test mode transactions use testnet networks, so no real funds are transferred.
Go Live
When you’re ready to accept real payments:
- Switch to live API keys - Replace
sk_test_keys withsk_live_keys from your dashboard - Configure your mainnet settlement address - Ensure your production wallet address is set
- Update webhook endpoints - Ensure your production webhook URL is configured
- Verify your integration - Run through the complete payment flow one more time
Important: Always test thoroughly in test mode before processing live transactions.
Next Steps
Now that you’ve created your first subscription:
- Create checkouts for one-time payments
- Sign subscription actions to charge and cancel
- Configure webhooks for all event types
- Explore the full API Reference for all available endpoints
