Skip to main content

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

ParameterRequiredDescription
redirectNoURL linked from the Continue to Merchant button shown after a successful subscription
themeNolight 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, and http://[::1] are permitted
  • Rejected: javascript: URIs, data: URIs, and relative URLs

Server-side verification

warning

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');
});
Eventually consistent

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 theme parameter
  • 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