License a trading indicator
This is a step-by-step technical guide to adding a license screen to your product: it has code to copy and API calls to make. If you sell the product but don't write its code, read Not a developer? Start here and send this page to your developer.
This covers MetaTrader 4 and 5 (indicators and expert advisors), NinjaTrader, and cTrader. A license is usually per trading account or per computer. The customer pastes the key into the indicator's inputs, the indicator activates, and it re-checks on a timer.
Licensr does not host your indicator file. Host it on your own website and link your buy page from there.
Pine Script cannot make web requests, so a TradingView script cannot call Licensr. TradingView has its own invite-only access list. Use Licensr for MetaTrader, NinjaTrader, and cTrader, and use the buy page plus a manual access step for TradingView.
Product settings
| Setting | Value |
|---|---|
| Activation mode | Seat: one activation per account or computer. |
| Client type | Native app. Trading terminals send no browser Origin. |
| Identifier | MetaTrader: the account number (AccountInfoInteger(ACCOUNT_LOGIN)). NinjaTrader and cTrader: a random ID you save once. |
Create a plan
In the admin panel, open your product and add a plan: one price with a limit on how many devices or websites one key can be used on. Keep the defaults if you are unsure. You can add more plans later. See the Quick start.
Also create a Client API key on the product (shown once, starts with pk_test_ or pk_live_). This is the key you put in your product.
Get a test license
You do not have to pay to try your integration. In the admin panel go to Customers → Issue license, enter your own email, pick the product and plan, and issue it. The key (starts with lic_) arrives by email and is shown once on screen. The dashboard checklist has a shortcut for this.
Use your pk_test_ key against https://api.staging.licensr.app while you build, then switch to pk_live_ and https://api.licensr.app when you release.
What your license screen must do
Your product needs one small screen: a text box for the key, an Activate button, and a Deactivate button. It follows these rules, whatever the platform:
- Activate, then validate. When the customer clicks Activate, call activate (this registers the device or website against the plan's limit), then validate. Unlock only if the validate response has
valid: true. Save the key and theactivation_idfrom the activate response. - Check again in the background. Call validate at every launch and about once a day while the product runs. A refund, a cancelled subscription, or an expired license happens after the sale, and this daily check is how your product finds out. Without it, a refunded key keeps working forever.
- Lock immediately on
valid: false. If Licensr cannot be reached, stay unlocked (optionally lock after a few days with no answer). Try again later. - Offer a Deactivate button. It calls deactivate with the saved
activation_id, forgets the key, and locks the product. This lets a customer move to a new computer without asking you for help.
Messages to show when activate fails (read detail.error in the response):
| Code | Say |
|---|---|
license_not_found | "We could not find that key. Check for typos." |
activation_cap_exceeded | "This key is already in use on the maximum number of devices. Deactivate it on another device first." |
license_inactive | "This license is no longer active. Contact the seller." |
| no response at all | "Could not reach the license server. Try again." |
| any other code | "Something went wrong (code)." Show the code so the customer can send it to you. |
The full list is in Errors.
MetaTrader 5 (MQL5)
MetaTrader blocks web requests until you allow the address. In the terminal go to Tools → Options → Expert Advisors, tick Allow WebRequest for listed URL, and add https://api.licensr.app (and the staging address while testing). Tell your customers to do the same, it is a one-time step.
input string LicenseKey = ""; // the customer pastes their key here
const string API = "https://api.staging.licensr.app"; // production: https://api.licensr.app
const string KEY = "pk_test_..."; // Client key, safe to ship
const string SLUG = "my-indicator";
bool g_licensed = false;
string g_activationId = "";
string Post(string path, string json)
{
char body[], result[];
StringToCharArray(json, body, 0, StringLen(json));
string headers = "Authorization: Bearer " + KEY + "\r\nContent-Type: application/json\r\n";
string responseHeaders;
int code = WebRequest("POST", API + path, headers, 5000, body, result, responseHeaders);
if(code == -1) return ""; // network problem: do not lock
return CharArrayToString(result);
}
// Extracts a simple string value from the JSON reply.
string Field(string json, string name)
{
int start = StringFind(json, "\"" + name + "\":\"");
if(start < 0) return "";
start += StringLen(name) + 4;
return StringSubstr(json, start, StringFind(json, "\"", start) - start);
}
bool Validate()
{
string reply = Post("/v1/license/validate",
"{\"license_key\":\"" + LicenseKey + "\",\"plugin_slug\":\"" + SLUG + "\"}");
if(StringFind(reply, "\"valid\":") < 0) return g_licensed; // no true/false answer (offline, rate limited, server error): keep the previous state
return StringFind(reply, "\"valid\":true") >= 0;
}
int OnInit()
{
// Activate, then validate.
string reply = Post("/v1/license/activate",
"{\"license_key\":\"" + LicenseKey + "\",\"plugin_slug\":\"" + SLUG +
"\",\"activation_type\":\"seat\",\"identifier\":\"" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + "\"}");
g_activationId = Field(reply, "activation_id");
g_licensed = (g_activationId != "") && Validate();
if(!g_licensed) Alert("License not active. Check the license key input.");
EventSetTimer(24 * 60 * 60); // check again once a day
return INIT_SUCCEEDED;
}
void OnTimer() { g_licensed = Validate(); }
void OnDeinit(const int reason)
{
EventKillTimer();
// Only deactivate when the customer removes the license, not on every chart change.
}
In your indicator's calculation, return early while g_licensed is false. To let a customer move accounts, add a second input such as DeactivateNow (set to true once) that calls /v1/license/deactivate with the saved activation ID.
NinjaTrader and cTrader
Both run C# add-ons. Create a random GUID once, save it to a file in your add-on's folder, and use it as the identifier.
- cTrader runs on modern .NET. Use the C# SDK the same way as in the desktop apps guide.
- NinjaTrader 8 runs on .NET Framework 4.8, where the C# SDK does not load. Make the three calls yourself with
HttpClient, as shown in the any-language guide.
Link your buy page
Licensr does not host your download. Host the file on your own website or store, then add a Buy button that links to your Licensr buy page (the dashboard shows the full link, which looks like /buy/your-slug). Customers pay there and get their key by email, then paste it into your license screen.
Go live
- Swap your
pk_test_key for thepk_live_key and usehttps://api.licensr.appin the release build. - Buy your own product once, using the buy page, to see the whole flow.
- Check the dashboard: the Get started checklist shows You're live! when payments, a product, a plan, a test license, and a first check from your product are all done.
Stuck? The license calls guide lists every field and error.