Skip to content

Commercial

License keys

Issuing keys, domain binding, expiry, and what happens when a key is missing.

What a Key Is

A license key is a string in three dot-separated parts:

OC1.<base64url payload>.<base64url signature>

The payload is JSON. The signature is Ed25519 over the payload string, made with ORTEX's private signing key, whose public half is embedded in @ortex-charts/core. Keys cannot be forged without that private key, which never leaves ORTEX's secret store.

interface LicensePayload {
  id: string;          // unique key id, for support and revocation
  customer: string;
  product: string;     // always "ortex-charts"
  tier: string;        // informational: "team", "whitelabel", "oem"
  features: string[];  // "whitelabel", "financial", "viz", "drawings", or "*"
  domains: string[];   // hostnames; "*.example.com" matches subdomains; "*" matches anything
  iat: number;         // issued at, Unix seconds
  exp: number;         // expiry, Unix seconds
}

Installing One

Call setLicenseKey once, as early as you can, before any chart is created. It is asynchronous because the signature check goes through WebCrypto, but you do not have to await it: the synchronous checks — format, product, expiry, domain — run first and the state is pending while the signature is verified, and features are granted during that window.

import { setLicenseKey } from "@ortex-charts/financial";

const status = await setLicenseKey(process.env.NEXT_PUBLIC_ORTEX_CHARTS_KEY!);
console.log(status.state, status.verified, status.message);

On the hosted kit this is already done: the kit file ends with a setLicenseKey call carrying your key, so nothing in your page has to know about it.

Where to Put the Key

The key is a public artifact. It ships to the browser, it is bound to your domains, and anyone can read it out of your bundle. Treat it like a publishable API key rather than a secret: an environment variable at build time is convenient and correct, and a secret store is unnecessary.

void setLicenseKey(import.meta.env.VITE_ORTEX_CHARTS_KEY);   // Vite
void setLicenseKey(process.env.NEXT_PUBLIC_ORTEX_CHARTS_KEY!); // Next.js

What is secret is the registry token in your .npmrc, which grants access to the package downloads. That one belongs in continuous-integration secrets and never in a commit.

The States

import { licenseStatus, onLicenseChange, hasFeature, clearLicense } from "@ortex-charts/financial";

licenseStatus();               // { state, payload, message, verified }
hasFeature("whitelabel");      // true only on a valid or pending license granting it
onLicenseChange((s) => console.log(s.state));
clearLicense();                // mostly for tests
state Meaning Effect
unlicensed No key has been set. The ORTEX mark shows; one console warning.
pending Payload accepted, signature still being checked. Features are granted; the chart draws normally.
valid Everything checked out. Features per the payload.
invalid Malformed, wrong product, or a signature that failed. Treated as unlicensed.
expired exp is in the past. Treated as unlicensed.
domain The current hostname is not covered. Treated as unlicensed.

verified is a separate boolean from state. It is true only once the Ed25519 signature has actually been checked. A browser whose WebCrypto lacks Ed25519 accepts the payload with state: "valid" and verified: false, because breaking a paying customer's chart over a missing platform primitive is worse than a missed check.

In every one of these states the chart works. Nothing is disabled, no data is degraded, and nothing appears on the user's screen except the mark. See the ORTEX mark.

Domains

domains is a list of hostnames the key covers.

  • An exact hostname matches itself: app.example.com.
  • A wildcard matches the base and every subdomain: *.example.com covers example.com, app.example.com and a.b.example.com.
  • * matches anything, and is what an OEM key usually carries.
  • localhost is always allowed, along with 127.0.0.1, ::1 and any *.localhost host, on every key. Development never fails a domain check.
import { domainAllowed } from "@ortex-charts/financial";

domainAllowed("app.example.com", ["*.example.com"]);   // true
domainAllowed("example.net", ["*.example.com"]);       // false

The check runs against location.hostname by default. Override it for tests:

await setLicenseKey(key, { hostname: "app.example.com", now: Date.parse("2027-01-01") });

Preview deployments are the case that catches people out. A branch preview on pr-482.vercel.app is not covered by *.example.com, so add the preview host as a wildcard domain when the key is issued, or accept the mark on previews.

Two Layers of Domain Enforcement

Hosted-kit customers are checked twice. The CloudFront function in front of the CDN compares the request Origin or Referer against the domains registered on the kit and refuses to serve the file to an unlisted host, and the key inside the file checks the hostname again in the browser. Registry customers get the second check only.

Expiry and Renewal

Keys are issued for the subscription term with a 14-day grace period added on top, so a renewal that is a few days late never flips a customer's chart to the mark. A renewal re-signs onto the same key id and the same kit id, so your script tag and your key id do not change from year to year — only the key string does, and hosted-kit customers do not even see that.

When a key does expire, the state becomes expired and the mark comes back. Nothing else changes. There is no countdown, no degradation and no interruption.

Under the White-label plan the last version released during the term keeps working with its key indefinitely, which is the perpetual fallback customers expect from this kind of agreement.

Features

Feature What it grants
whitelabel branding.visible = false is honored, and the mark may be replaced with your own.
financial Informational; the financial package is not gated.
viz Informational; the viz package is not gated.
drawings Informational; drawing tools are not gated.
* Everything, including whitelabel.

Being precise about this: only whitelabel changes behavior today. The others are recorded on the key so that the plan a customer is on is visible in the payload, and so that a future gate has a name to use, but no feature check in the library disables a package. A Community key and a White-label key run the same code with the same capabilities; the mark is the difference.

Getting a Key

  • Community, Team and White-label keys are issued automatically. A purchase on ortexcharts.com signs a key, builds a hosted kit, registers your domains, issues a registry token and emails all of it within seconds of payment. Community follows the same path without the payment step.
  • OEM and enterprise keys are issued by hand from the same signer after a conversation.

Changing the domains on a key means re-issuing it, which happens automatically when the domains on the subscription change; the kit is rebuilt at the same URL.

Revocation

Every payload carries an id. A key that has to be killed before its expiry is handled by publishing a revocation list in a release rather than by a runtime call to ORTEX, which would mean every customer page depending on ORTEX uptime. The practical implication for a customer is that revocation takes effect on the next library update — and for a hosted kit, that the kit simply stops being served.

Testing Without a Key

The library is fully functional unlicensed, so evaluation, unit tests and continuous integration need nothing. localhost is always allowed, so local development with a real key never fails a domain check either.

For tests that need a specific license state:

import { clearLicense, setLicenseKey, licenseStatus } from "@ortex-charts/financial";

beforeEach(() => clearLicense());

it("hides the mark under a white-label key", async () => {
  await setLicenseKey(TEST_KEY, { publicKey: TEST_PUBLIC_KEY, hostname: "example.com", now: FIXED_TIME });
  expect(licenseStatus().state).toBe("valid");
});

publicKey overrides the embedded key so a test can sign with its own pair, and now and hostname make expiry and domain behavior deterministic.