Skip to main content

Merchant Integration

There are two ways to set up amser: through the dashboard (no code required for setup) and through direct contract calls (for developers who want to automate deployment or integrate it into their own tooling). The dashboard path is recommended for most merchants. Both paths produce the same result on-chain — a deployed module with one or more plans, ready to accept subscribers.

This walkthrough covers everything from connecting your wallet to verifying your first subscriber. No code is required until the verification step.

Step 1 — Connect your wallet

Go to app.amser.io and connect the wallet you want to use as your merchant wallet. This wallet address becomes the merchant address on all modules you deploy — it is the address that receives payments and can manage plans. Use a wallet you control and intend to keep, not a throwaway.

Authenticate using SIWE (Sign-In With Ethereum): you'll be prompted to sign a message in your wallet. No gas is spent at this step. See the Authentication guide for details on how SIWE works.

Step 2 — Deploy a module

From the dashboard, navigate to Modules and click Deploy Module. Choose Subscription for time-based recurring billing, or Credit for usage-based billing. One module is deployed per module type per merchant wallet.

This step requires a single on-chain transaction — the dashboard will prompt your wallet to confirm it. The transaction deploys a minimal proxy contract pointing to the amser module implementation and registers it with the PaymentProcessor.

info

Deploying a module costs a small amount of gas (around 200k gas on Base). Make sure your wallet has enough ETH to cover it.

Step 3 — Create a plan

Once your module is deployed, go to Plans and click New Plan. Fill in:

  • Price — the amount charged per billing cycle, in USDC (or whichever token you select).
  • Interval — how often the charge recurs: daily, weekly, monthly, or a custom number of seconds.
  • Grace period — how long subscribers retain access after a missed payment before their subscription lapses. 7 days is a reasonable default for most SaaS products.
  • Token — the payment token. USDC is available on all supported chains.
  • Name and description — shown to subscribers on the hosted checkout page.

Plans are immutable once created — price, interval, and token cannot be changed. Only the active/inactive flag can be toggled. If you need to change pricing, create a new plan and direct new subscribers to it. Existing subscribers remain on their original plan.

Creating a plan costs a small on-chain transaction.

Step 4 — Send users to checkout

With your module deployed and a plan created, you can start accepting subscribers. The hosted checkout page handles everything: wallet connection, ERC-20 approval, Permit2 signing, and subscription signing.

Copy the checkout URL from the plan detail page in the dashboard, or construct it manually:

https://app.amser.io/subscribe/<moduleAddress>/<planId>

Add this link to your pricing page, email campaigns, or any other acquisition surface. See the Hosted Checkout guide for the full URL parameter reference and how to handle the post-checkout return flow.

Step 5 — Configure webhooks

Go to Settings → Webhooks and add your server's endpoint URL. Select the events you want to receive — at minimum, subscribe to payment.succeeded and payment.failed. amser will POST a signed JSON payload to your URL whenever these events occur.

See the Webhooks documentation for the full event reference and signature verification.

Step 6 — Verify a subscription

Once subscribers start coming through, you have two ways to verify their status server-side:

// Option A: API check — no wallet needed, just an API key
const params = new URLSearchParams({
module_address: moduleAddress,
wallet: userAddress,
plan_ids: planId,
mode: 'indexed',
});

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();
// Option B: on-chain check — authoritative, requires an RPC endpoint
import { createPublicClient, http, parseAbi } from 'viem';
import { baseSepolia } from 'viem/chains';

const client = createPublicClient({ chain: baseSepolia, transport: http() });

const isActive = await client.readContract({
address: moduleAddress,
abi: parseAbi(['function isActive(address,uint256) view returns (bool)']),
functionName: 'isActive',
args: [userAddress, BigInt(planId)],
});

Option A is fast (~5ms) and requires no blockchain infrastructure. Option B is authoritative but requires an RPC call. For most access control decisions, Option A is sufficient — use Option B for high-stakes checks or when you need certainty immediately after a state change. See the API Keys documentation for the full response shape.

Step 7 — Test end-to-end

Testnet first

amser is currently deployed on testnets. Use Base Sepolia for integration testing.

On Base Sepolia testnet, subscribe as a test user using the hosted checkout. The keeper runs on a short poll interval on testnet — your first payment should execute within a few minutes. Check the Subscriptions tab in the dashboard, verify the webhook fires, and confirm the access check returns authorized: true.


Part 2 — Programmatic setup (for developers)

This section covers the same steps as above using direct contract calls via viem. Use this path if you want to automate module deployment as part of a script, integrate amser into your own onboarding flow, or avoid using the dashboard entirely.

Prerequisites: a merchant wallet private key available in your environment, Node.js 20+, and viem v2 (npm install viem).

Deploy a module

import { createWalletClient, createPublicClient, http, parseAbi } from 'viem';
import { baseSepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const FACTORY_ADDRESS = '0x...'; // from /contracts
const MODULE_ID_SUBSCRIPTION = 0n;

const account = privateKeyToAccount(process.env.MERCHANT_PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({ account, chain: baseSepolia, transport: http() });
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });

const txHash = await walletClient.writeContract({
address: FACTORY_ADDRESS,
abi: parseAbi(['function deployModule(uint256 moduleId) returns (address module)']),
functionName: 'deployModule',
args: [MODULE_ID_SUBSCRIPTION],
});

After the transaction confirms, get your module address:

const moduleAddress = await publicClient.readContract({
address: FACTORY_ADDRESS,
abi: parseAbi(['function getModule(address merchant, uint256 moduleId) view returns (address)']),
functionName: 'getModule',
args: [account.address, MODULE_ID_SUBSCRIPTION],
});

Create a plan

import { parseUnits } from 'viem';

const USDC_ADDRESS = '0x...'; // testnet USDC — see /contracts
const PRICE = parseUnits('10', 6); // 10 USDC
const INTERVAL = 30 * 24 * 60 * 60; // 30 days in seconds
const GRACE_PERIOD = 7 * 24 * 60 * 60; // 7 days in seconds
const METADATA_HASH = `0x${'00'.repeat(32)}` as `0x${string}`; // bytes32(0) for now

const txHash = await walletClient.writeContract({
address: moduleAddress,
abi: parseAbi([
'function createPlan(uint256 price, uint256 interval, uint256 gracePeriod, address token, bytes32 metadataHash) returns (uint256 planId)'
]),
functionName: 'createPlan',
args: [PRICE, BigInt(INTERVAL), BigInt(GRACE_PERIOD), USDC_ADDRESS, METADATA_HASH],
});

Plan IDs start at 1 and increment. For production use, replace bytes32(0) with a real metadata hash — compute it off-chain from canonical JSON using the hashPlanMetadata utility, then call POST /v0/modules/:moduleAddress/plans/:planId/metadata to store the metadata and make it visible in the dashboard.

From here, it's the same

Once the module and plan exist on-chain, the rest of the integration is identical to the dashboard path described in Part 1. Use the hosted checkout to accept subscribers, configure webhooks for event notifications, and verify subscriptions via the API or on-chain calls.

Next steps