Reference for how each O2ODDS EA validates its license against the central server. This page exists mainly so paying customers can audit exactly what the EA calls and what it stores. Compiled EAs ship with this integration built in — no manual coding required.
All license calls go to a single HTTPS endpoint. HTTPS is required — plain HTTP is refused by .htaccess.
POST https://o2odds.com/api/license.php
JSON body:
{
"action": "activate" | "validate" | "deactivate",
"key": "O2X-XXXX-XXXX-XXXX-XXXX",
"account": "12345678",
"platform": "MT4" | "MT5" | "cTrader",
"nonce": "random-string-per-request"
}
Always JSON, always HTTP 200 (except 405 for wrong method and 429 when rate-limited). The EA must inspect status, not just the HTTP code.
{
"ok": true,
"status": "active",
"product": "apiary",
"activations_used": 1,
"activations_max": 3,
"valid_until": null,
"timestamp": 1720012345,
"nonce": "the-same-nonce-you-sent",
"signature": "hmac-sha256-hex"
}
The EA must verify the signature field on every response before trusting it. This prevents a spoofed server or a man-in-the-middle from returning a fake active. The signature is:
signature = HMAC_SHA256( key + "|" + account + "|" + platform + "|" + status + "|" + valid_until + "|" + nonce + "|" + timestamp, LICENSE_API_SECRET )
Where LICENSE_API_SECRET is a 64-character hex string compiled into the EA binary. When it needs rotating (say, if a build leaks), publish a new EA build with the new secret and update db-config.php in the same window.
The EA should cache the last successful response and permit continued operation for a short offline window (default 3 days). This handles brief outages without killing trading. After the grace period expires without a successful check, the EA halts.
// MQL4 — outline only. Actual EA builds ship with this ready-integrated.
#property strict
input string LicenseKey = "O2X-XXXX-XXXX-XXXX-XXXX";
int OnInit()
{
if(!LicenseActivate(LicenseKey))
{
Print("License activation failed. See journal.");
return(INIT_FAILED);
}
EventSetTimer(3600); // heartbeat every hour
return(INIT_SUCCEEDED);
}
void OnTimer()
{
LicenseValidate(LicenseKey);
}
void OnDeinit(const int reason)
{
EventKillTimer();
if(reason == REASON_REMOVE) LicenseDeactivate(LicenseKey);
}
bool LicenseActivate(const string key)
{
string body = BuildJson("activate", key);
string resp;
if(!CallLicenseApi(body, resp)) return(ApplyOfflineGrace());
return(VerifyAndAccept(resp));
}
// CallLicenseApi() uses WebRequest() with POST, application/json.
// VerifyAndAccept() parses JSON, checks the HMAC signature, caches
// the response with a wall-clock timestamp, then returns the ok flag.
// Full implementation lives in the compiled EA; this outline is just
// a reference for customer-side audit.
The MQL5 version uses WebRequest() the same way. The cTrader (cAlgo / C#) version uses HttpClient with application/json — semantically identical.
Every API call is logged to license_events with the license (or license key on failure), action, IP address, account, platform, and outcome. Customers can see the resulting activations on their licenses dashboard.
The API stores the broker account number, platform name, and IP address of the machine running the EA — nothing else from the EA. See the privacy policy for the full list of what we collect and why.