Create Top-up Session
POST /io/wallet/topup-session — create a Stripe Checkout session to buy wallet credits. 1 credit = $1.
Creates a Stripe Checkout session for a whole-dollar credit top-up and returns the hosted checkout URL. Redirect the user to that URL to pay. Credits are granted when Stripe confirms the payment via the webhook — not when this endpoint returns.
POST https://api.misar.io/io/wallet/topup-session
Service key only
This endpoint requires x-wallet-service-key. Your backend creates the session; the browser never holds the service key.
Authentication
x-wallet-service-key: <WALLET_SERVICE_KEY>
Content-Type: application/json
Body parameters
user_idstringbodyrequiredThe Misar SSO user id the credits will be granted to once payment completes.
amountDollarsintegerbodyrequiredWhole-dollar top-up amount. Must be an integer in the range 10–100000 ($10 minimum, $100,000 maximum per transaction). Because 1 credit = $1, this is also the number of credits the user receives.
productstringbodyrequiredThe originating product slug (e.g. blog, mail, reach). Recorded on the session for attribution.
returnUrlstringbodyrequiredThe URL Stripe returns the user to after checkout (success or cancel).
Request
curl -X POST "https://api.misar.io/io/wallet/topup-session" \
-H "x-wallet-service-key: $WALLET_SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_123",
"amountDollars": 25,
"product": "blog",
"returnUrl": "https://www.misar.blog/dashboard/billing"
}'Response
{
"url": "https://checkout.stripe.com/c/pay/cs_live_...",
"baseCredits": 250,
"bonusCredits": 15,
"totalCredits": 265,
"bonusPercent": 6,
"tierLabel": "6% bonus credits"
}urlstringThe Stripe-hosted Checkout URL. Redirect the user here to complete payment.
baseCreditsnumberBase credits = amountDollars (1 credit = $1).
bonusCreditsnumberExtra credits from the applicable volume-discount tier, rounded to 2 decimals.
totalCreditsnumberbaseCredits + bonusCredits. A preview only — the actual grant is floored to a whole credit at payment time.
bonusPercentnumberThe bonus percentage applied.
tierLabelstringDisplay label of the applied tier, e.g. "6% bonus credits".
Credit preview is informational
The *Credits fields let your UI show "you'll get N credits" before checkout. They do not determine the grant — the webhook recomputes credits from the Stripe-confirmed amount and never trusts client- or session-supplied values.
Billing details, tax, and invoices
Every checkout session collects the buyer's compliance details and produces a receipt:
- Billing address — always required at checkout (
billing_address_collection: required). The billing country is recorded on the resulting ledger entry. - Tax ID collection — business buyers can attach their VAT/GST/tax ID at checkout; it appears on the generated invoice.
- Stripe invoice — when the tax flag is set, an invoice is generated for the top-up (
invoice_creation). Its hosted receipt URL is stored on the ledger entry and exposed asreceipt_urlon List Transactions. - Automatic tax — when the wallet service runs with
WALLET_STRIPE_TAX_ENABLED=1, Stripe Tax computes and charges tax on top of the credit price (tax_behavior: exclusive). Tax never changes the credits granted — the webhook derives credits fromamount_total − amount_tax.
Automatic tax and invoicing are flag-gated
automatic_tax and invoice_creation are only sent to Stripe when the WALLET_STRIPE_TAX_ENABLED=1 environment flag is set, because Stripe Tax must first be activated in the Stripe dashboard (origin address and tax registrations configured) and invoice generation carries a per-invoice fee. Billing-address collection and tax-ID collection are always on regardless of the flag; with the flag off there is no invoice and the ledger's invoice columns stay NULL.
Validation
amountDollars must be an integer within [10, 100000]. Non-integer or out-of-range values are rejected with 400 before any Stripe session is created:
"amount_dollars must be an integer""Minimum top-up is $10""Maximum top-up per transaction is $100,000"
Rate limit
50 write ops/user/60 s. Exceeding the limit returns 429 with a Retry-After header.
Credits are granted on payment, not on session creation
A successful response only means a checkout session was created. The user's balance increases only after Stripe fires checkout.session.completed to the wallet webhook, which credits floor(amount_total / 100 + bonus) — the base credits plus the applicable volume-discount bonus.