Hosted Checkout
The hosted checkout is a pre-built subscription page hosted by amser at app.amser.io. It handles wallet connection, ERC-20 approval, Permit2 signing, and the subscribeWithPermit() call in a single user flow. Merchants link to it rather than building their own checkout UI.
URL format
https://app.amser.io/subscribe/<moduleAddress>/<planId>
Replace <moduleAddress> with the address of your deployed SubscriptionModule and <planId> with the numeric plan ID. Both values are visible in the dashboard on the plan detail page.
Query parameters
| Parameter | Required | Description |
|---|---|---|
redirect | No | URL linked from the Continue to Merchant button shown after a successful subscription |
theme | No | light or dark. Defaults to dark |
Redirect behaviour
When a redirect URL is provided, the success screen shows a Continue to Merchant button that links to your URL with subscription_id and tx_hash appended as query parameters. The user is not redirected automatically — they must click the button.
If no redirect is provided, the button instead reads Go to Dashboard and returns the user to the amser dashboard.
Full example URL:
https://app.amser.io/subscribe/0xabcd.../1?redirect=https%3A%2F%2Fyourapp.com%2Fbilling%2Fcomplete&theme=light
When the user clicks Continue to Merchant, they are taken to:
https://yourapp.com/billing/complete?subscription_id=42&tx_hash=0xef01...
Redirect URL security rules
- Production: must use
https:// - Local development:
http://localhost,http://127.0.0.1, andhttp://[::1]are permitted - Rejected:
javascript:URIs,data:URIs, and relative URLs
Server-side verification
Never grant access based solely on subscription_id or tx_hash in the URL. These parameters are informational only and can be manipulated. Always verify subscription status independently before granting access.
The correct pattern is to verify subscription status via the amser API when the user arrives at your endpoint:
async function checkSubscription(
userAddress: string,
maxRetries = 3,
baseDelayMs = 1000,
): Promise<boolean> {
const params = new URLSearchParams({
module_address: MODULE_ADDRESS,
wallet: userAddress,
plan_ids: PLAN_ID,
});
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(
`https://api.amser.io/v0/auth/check?${params}`,
{ headers: { 'Authorization': `Bearer ${process.env.AMSER_API_KEY}` } }
);
const { authorized } = await res.json();
if (authorized) return true;
if (attempt < maxRetries) {
await new Promise((r) => setTimeout(r, baseDelayMs * 2 ** attempt));
}
}
return false;
}
app.get('/billing/complete', async (req, res) => {
const userAddress = req.session.address; // from your auth layer
const authorized = await checkSubscription(userAddress);
if (!authorized) {
return res.status(403).send('Subscription not confirmed yet — please try again shortly.');
}
req.session.subscribed = true;
res.redirect('/dashboard');
});
authorized may briefly return false immediately after the user arrives if the indexer hasn't yet processed the subscription event. The example above retries with exponential backoff (1 s → 2 s → 4 s) before giving up. Adjust maxRetries and baseDelayMs to match your tolerance.
Limitations
The hosted checkout does not currently support:
- Pre-filling the user's wallet address
- Custom branding beyond the
themeparameter - Embedding as an iframe
For full control over the subscription UX, implement the Permit2 flow directly — reference the Permit2 concepts page for the typed data structure and signing details.
Next steps
- Merchant Integration — Full end-to-end setup guide
- Authentication — SIWE, API keys, and on-chain gating patterns
- Webhooks — Receive real-time notifications for subscription events
- Permit2 — Understand the authorization model behind the checkout flow