Browse the library
Documentation libraryLicensing
Customer portal
The customer portal is a self-service page in your app's name and brand colour where buyers check their term, plan and machines, and free an old machine to move to a new one, without writing to you.
7 min read
What the portal is for
Most after-sale support is the same two questions: "when does my key run out?" and "I changed computers, can you remove the old one?". The portal answers both, within the transfer rules you set, and every removal still lands in your activity log and fires a webhook.
Each app has its own portal. It has no dashboard chrome, buyers need no ClavisAUH account, and search engines are told not to index it.
Turning the portal on
- Open the app and go to Licensing & releases › Sign-in & license plans.
- At the bottom of the page, find the Customer portal card and flip the switch in its corner.
- The portal address appears right below, with a copy button and an Open portal button that opens it in a new tab.
- Pick a Brand colour. It is saved when you leave the colour picker, as a hex value
#rrggbb. Until you pick one, the portal uses#0f9e8f.
The brand colour paints the sign-in tabs, the buttons, the diamond next to your app name and the status dots. Your app name leads the page; ClavisAUH only appears in the "Protected by ClavisAUH" footer line.
The portal address
The address looks like https://<host>/portal/app_…, where app_… is your app ID. Copy it exactly from the card and put it where buyers will find it: the delivery email, your download page, the help menu of your software. For example, a "Manage license" button in an Electron app:
const { shell } = require("electron");
// Paste the exact address copied from the Customer portal card
const PORTAL_URL = "https://<host>/portal/app_…";
manageLicenseButton.addEventListener("click", () => shell.openExternal(PORTAL_URL));
While the portal is on, the "license expires soon" email sent to buyers also carries a button to it. Buyers can switch between English and Vietnamese with the toggle in the top corner.
Linking a buyer email to a license
Email sign-in only works for licenses that carry the buyer's email. You can set it three ways:
- In the dashboard: go to Licensing & releases › Licenses, open a license, and in the Buyer row click Add an email to open the customer portal, type the address and click Save. Clear the field and save to remove it.
- With the Seller API: pass
emailwhen you create the license. - Through a reseller: resellers fill in Buyer email when they issue keys in Business › Reseller portal.
curl -X POST https://<host>/seller/v1/apps/app_…/licenses \
-H "Authorization: Bearer clv_live_…" \
-H "Idempotency-Key: order-1001" \
-H "Content-Type: application/json" \
-d '{ "policyId": "pol_…", "count": 1, "email": "buyer@example.com" }'
Emails are always stored in lower case, so it does not matter how the buyer capitalises theirs.
How buyers sign in
The sign-in page has two tabs: By email and By key.
By email
- The buyer enters the Email used at purchase and clicks Send sign-in link.
- If that address is on at least one license of the app, a one-time link valid for 15 minutes is sent.
- Opening the link goes straight into the portal. The token is dropped from the address bar at once, and the link cannot be used again.
Whether or not the address bought anything, the screen shows the same "If ... bought something, a sign-in link is on its way", so a stranger cannot probe who your customers are. An expired or used link shows "This link expired or was already used" with a Send a new link button that leads back to sign-in.
Signed in by email, the buyer sees every license of the app linked to that address (up to the 20 newest).
By key
The buyer pastes the License key and clicks Continue:
- The license has an email: the key alone is not enough. A link goes to the license email and the screen only shows a masked address such as
a•••@d•••.com. Someone holding a leaked key cannot get in. - The license has no email: the buyer is in right away and sees that one license only.
A wrong key shows "This key is not valid". After 10 wrong keys from the same IP within about 15 minutes, the form answers "Too many tries, wait a few minutes". On top of that, the sign-in forms accept at most 5 requests a minute from one IP.
What buyers see
Each license is a card:
| Item | How it shows |
|---|---|
| Key | A short preview, never the full key |
| Plan | The plan name, or "Custom plan" when the license has none |
| Status | Active, Ending soon, In grace, Not activated, Expired, Suspended, Revoked |
| Term | "until <date>" or "lifetime" |
| Entitlements | The entitlement keys the plan grants |
| Machines in use | Machine name (or "Unnamed machine"), platform, country, last seen |
"Ending soon" appears once the license is inside the plan's Notice before the end (days) window, 7 days by default. "In grace" means the end date has passed but the plan's grace days have not. A machine that has been silent too long reads "asleep": it already gave its seat back under the plan's Silent machine frees its seat after (hours) setting.
Below the cards, Recently lists the 15 latest events on those licenses, such as Activation, Sign-in, Trial, Removed from the portal or SDK tamper report, with country and time. A dot in your brand colour means it succeeded; a red dot means it did not.
Removing a machine within the transfer rules
Each machine has a Remove button. The buyer confirms "Remove ... from the license? It will need to activate again"; the machine is then removed, all its sessions end and the seat is free for a new machine. To use the software on that machine again, the buyer has to activate again.
The transfer rules are taken in this order:
- The Device transfers section of the license plan (Per 30 days, Hours between transfers, and the Seller, Buyer and Reseller boxes).
- When the plan leaves the count or the wait empty: 2 moves per 30 days, 24 hours apart.
The portal shows those rules like this:
| Situation | Status line | Remove button |
|---|---|---|
| Moves left, no wait running | "x/y moves left in 30 days" | Enabled |
| No moves left, or inside the wait | "Next move <time>" | Greyed out |
| The plan does not let the Buyer move | "Ask the seller to move a machine" | Hidden |
Buyer and reseller moves share one allowance. The SDK's transfer() call in the JavaScript SDK, where the software frees its own seat before a move, also counts as a buyer move. Removals you make in the dashboard are never limited and never use up the buyer's moves. To keep moves to yourself, untick Buyer in the plan. If all three boxes are unticked, the plan falls back to Seller and Buyer. Removing an asleep machine still costs a move, even though it already gave its seat back.
Every time a buyer removes a machine, you get:
- A
portal.device_releaseevent in App security › Activity log. - A
device.transferredwebhook whose payload carrieslicenseId,deviceIdand"by": "customer". - The buyer's move count in the Device transfers section of the license details.
Session length
- The email link lives 15 minutes and works once.
- A portal session lasts 30 minutes from sign-in and is not extended by activity. After that, the portal goes back to the sign-in screen.
- The session sits in an HttpOnly cookie scoped to the app. Sign out ends it immediately.
The plan limit
The portal is governed by the workspace limit customerPortal, shown as the Customer portal row in Account & organization › Plan & usage. With the default configuration, both Basic and Premium include it. When the workspace plan does not:
- Turning the switch on is refused with the error code
plan_limit. - A portal that is already on stops opening; buyers see "This portal is closed or does not exist", exactly as when you switch it off.
See Basic, Premium and limits and the pricing page for every limit.