# Customer portal

A hosted page, on your own domain and under your brand, where your customers find their license keys and free their machines without emailing you.

Every account gets a customer portal. Your customers sign in with their email address, see their licenses and keys, and free a computer to move their license. It is yours: your name, your logo, your colour, and, once your sending domain is verified, your own address.

```text
https://keyrail.tech/portal/{accountSlug}        until your own address is set up
https://account.yourdomain.com/         with your own address
```

Link to it from your app’s “Manage license” menu, your website and your purchase emails (license-key emails already link to it).

## What your customers can do

1. Enter their email address. They receive a sign-in link, valid 15 minutes and usable once (no password, no account to create).
2. See each license: product, plan, status in plain words (“Renews on 12 March 2027”, “Lifetime license – version 1”, “Payment pending”), the key (masked, with **Show** and **Copy**).
3. See the computers using each license (“1 of 2 computers in use”, name, system, last activity) and **free** one, after a confirmation that explains what happens.
4. Open your billing portal (“Manage my subscription and invoices”) if you set its address.

The portal is in English and French: `?lang=en` or `?lang=fr` in the link, else the browser’s language, else your account’s default language (**Account & usage › Customer e-mails**). It works on phones, in light and dark mode, with the keyboard and screen readers, and without JavaScript.

## Your own address

In **Account & usage › Customer portal**, set the **Portal address** to a subdomain of your verified sending domain, for example `account.yourdomain.com` (see [Custom sender](/docs/custom-sender/) for the domain verification: it is also the proof that the address is yours).

1. **Route the address to Keyrail.** Today this is done by Keyrail on its Cloudflare account (a *Workers Custom Domain*): write to us with the address; Cloudflare creates the DNS record and the certificate. Self-serve setup for any DNS provider (a `CNAME`) is planned.
2. **Open it once.** The first visit tells Keyrail that the address works: from then on, license-key emails and sign-in links point to it, and old `https://keyrail.tech/portal/…` links redirect to it.

On your address, only your portal answers: every other path is “not found”, nothing of Keyrail is shown there. If your sending domain stops verifying, the address is switched off and emails fall back to the Keyrail address until it verifies again.

## Your brand

| Setting | Effect |
|---|---|
| Name shown | Header and page titles (default: your sender name). |
| Brand colour | Buttons and accents. The text on buttons is chosen for contrast (WCAG AA), and a mid-tone that would be hard to read is darkened slightly: any colour works. |
| Logo | An https address on your verified domain, or a PNG or SVG file of up to 50 KB uploaded in the dashboard (SVG without scripts, links or external references). |
| Billing portal | Your payment provider’s customer portal, e.g. Paddle’s: shown as “Manage my subscription and invoices”. |
| Hide “License management by Keyrail” | Removes the mention from the portal footer and from license-key and sign-in emails. |

## “Lost your key?” on your website

Your customers can get their key again from your own website. Turn it on in **Account & usage › “Lost your key?” on your website**, list the exact addresses of your website (for example `https://www.yourdomain.com`; `http://localhost:<port>` is accepted for testing), and call:

```js
const response = await fetch("https://api.keyrail.tech/v1/public/resend-key", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ productId: "prd_...", email: emailInput.value, locale: "en" }),
});
// 202 in every accepted case, known address or not: show the same message.
```

Keyrail emails the key of each license of that product to **the address registered with the license**, never to another one, with your sender, language and portal link. The answer never says whether the address is known and never contains a key. `400` for an invalid address, `429` after 3 requests per hour for an address (or 20 per hour from one visitor), `403` for a website that is not in your list (calls without a browser origin are refused too).

## Or a page inside your website

Prefer to keep customers on your own website, under your own menu? Put a **My license** page there: two lines to paste, the same sign-in by email link, licenses, keys and **Free this machine**. See [License page on your website](/docs/license-page/).

## Your own portal, or none

Already using your payment provider’s portal (for example the Paddle customer portal, where customers sign in with their email)? In **Account & usage › Customer e-mails › Customer portal linked from the e-mails**, choose **My own portal** and paste its https address, or **No portal link**. License-key emails then link to your portal (or to none) and do not mention Keyrail, and the hosted portal of your account is switched off (it answers “not found”). You keep the two support gestures in the dashboard, on each license: **Send key again** and **Free this machine**.

## Security

The sign-in link opens a session stored in the `__Host-kr_portal` cookie: 7 days, `HttpOnly`, `Secure`, `SameSite=Lax`, bound to the portal’s own address. The email address entered is never confirmed or denied on screen, so the portal cannot be used to find out who your customers are. Pages load nothing from other sites (strict Content Security Policy, no inline script) and cannot be framed.

## Why it matters

Most licensing support emails are “I lost my key” and “I changed computers”. The portal answers both without you.
