License Verification Guide
This guide explains how to implement ECOSIRE license checking in your own application — whether you are building an Odoo module, a SaaS product, or any software that should be gated behind a purchased license.
Introduction
The ECOSIRE licensing system uses:
- License keys in the format
PREFIX-XXXX-XXXX-XXXX-XXXX(e.g.,SHOP-A1B2-C3D4-E5F6-G7H8) - Domain binding — each activation ties a license to a specific domain/installation
- HMAC signing — requests are signed with SHA256 to prevent forgery
- Activation limits — a license allows a configurable number of concurrent activations
Prerequisites
- ECOSIRE account with licenses issued (via purchase or admin grant)
ECOSIRE_LICENSE_SECRETfrom your ECOSIRE admin settings- Python 3.9+ or any language that supports HMAC-SHA256
Step 1 — Understand the License Lifecycle
License issued (on purchase or admin grant)
↓
Customer enters license key in your app
↓
App calls POST /licenses/validate ← check if key is valid
↓
App calls POST /licenses/activate ← bind domain to license
↓
App periodically re-validates (weekly heartbeat)
↓
License expires or is revoked → app disables features
Step 2 — Validate a License Key
Before allowing use, check if the key is valid:
import hmac, hashlib, time, requests, os
LICENSE_SECRET = os.environ.get("ECOSIRE_LICENSE_SECRET", "")
API_BASE = "https://api.ecosire.com/api"
def build_signature(payload: dict) -> str:
"""HMAC-SHA256 sign the payload sorted keys as query string."""
message = "&".join(f"{k}={v}" for k, v in sorted(payload.items()))
return hmac.new(
LICENSE_SECRET.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
def validate_license(key: str, domain: str) -> dict:
payload = {
"key": key,
"domain": domain,
"timestamp": str(int(time.time())),
}
signature = build_signature(payload)
resp = requests.post(
f"{API_BASE}/licenses/validate",
json=payload,
headers={"X-Ecosire-Signature": signature},
timeout=10,
)
return resp.json()
result = validate_license("SHOP-A1B2-C3D4-E5F6-G7H8", "mystore.myshopify.com")
if result.get("valid"):
print("License valid! Status:", result["license"]["status"])
else:
print("Invalid license:", result.get("message"))
Step 3 — Activate a License
Activation binds the license to a specific domain or installation fingerprint. Call this once when the user first installs your software.
def activate_license(key: str, domain: str) -> dict:
payload = {
"key": key,
"domain": domain,
"timestamp": str(int(time.time())),
}
signature = build_signature(payload)
resp = requests.post(
f"{API_BASE}/licenses/activate",
json=payload,
headers={"X-Ecosire-Signature": signature},
timeout=10,
)
return resp.json()
result = activate_license("SHOP-A1B2-C3D4-E5F6-G7H8", "mystore.myshopify.com")
if result.get("activated"):
print(f"Activated! Slots used: {result['activationsCount']}/{result['activationsLimit']}")
else:
print("Activation failed:", result.get("message"))
Step 4 — Implement in an Odoo Module
The ECOSIRE Odoo client module (ecosire_license_client) handles this automatically. For custom modules, add a license check to your __init__.py:
# your_module/__init__.py
import requests, logging, odoo
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
def check_license():
try:
config = odoo.tools.config
license_key = config.get("ecosire_license_key", "")
site_url = config.get("web.base.url", "")
if not license_key:
_logger.warning("ECOSIRE license key not configured.")
return
resp = requests.post(
"https://api.ecosire.com/api/licenses/validate",
json={"key": license_key, "domain": site_url},
timeout=5,
)
data = resp.json()
if not data.get("valid"):
raise ValidationError(f"License invalid: {data.get('message', 'Unknown error')}")
_logger.info("ECOSIRE license validated successfully.")
except requests.exceptions.RequestException:
_logger.warning("Could not reach ECOSIRE license server — using grace period.")
check_license()
Add the license key to odoo.conf:
ecosire_license_key = SHOP-A1B2-C3D4-E5F6-G7H8
Step 5 — Implement a Heartbeat Check
Re-validate weekly to catch revoked or expired licenses:
import threading, time
def license_heartbeat(key: str, domain: str, interval_seconds: int = 604800):
"""Re-validate every 7 days."""
while True:
time.sleep(interval_seconds)
result = validate_license(key, domain)
if not result.get("valid"):
# Disable premium features
disable_premium_features()
break
thread = threading.Thread(target=license_heartbeat, args=("SHOP-A1B2-...", "mystore.myshopify.com"), daemon=True)
thread.start()
Step 6 — Offline Grace Period
When the ECOSIRE API is unreachable, allow a grace period (7–30 days) before disabling features:
import json
from pathlib import Path
from datetime import datetime, timedelta
CACHE_FILE = Path("/tmp/ecosire_license_cache.json")
GRACE_DAYS = 7
def validate_with_cache(key: str, domain: str) -> bool:
try:
result = validate_license(key, domain)
if result.get("valid"):
CACHE_FILE.write_text(json.dumps({
"valid": True,
"cachedAt": datetime.utcnow().isoformat(),
}))
return True
return False
except Exception:
# Offline — check cache
if CACHE_FILE.exists():
cache = json.loads(CACHE_FILE.read_text())
cached_at = datetime.fromisoformat(cache["cachedAt"])
if datetime.utcnow() - cached_at < timedelta(days=GRACE_DAYS):
return cache.get("valid", False)
return False
Step 7 — Deactivate on Uninstall
When the user removes your software, release the activation slot:
def deactivate_license(key: str, domain: str):
payload = {"key": key, "domain": domain, "timestamp": str(int(time.time()))}
signature = build_signature(payload)
requests.post(
f"{API_BASE}/licenses/deactivate",
json=payload,
headers={"X-Ecosire-Signature": signature},
timeout=10,
)
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
License not found | Incorrect key format | Verify format: PREFIX-XXXX-XXXX-XXXX-XXXX |
Activation limit reached | All slots used | User must deactivate another domain first |
Invalid signature | LICENSE_SECRET mismatch | Copy secret from ECOSIRE admin settings |
License expired | Past expiry | Customer should renew from their dashboard |
License suspended | Subscription past due | Customer must update payment method |
Next Steps
- Licenses API — Full license endpoint reference
- Webhooks Reference — Real-time license event notifications
- Stripe Webhooks Guide — Automatic license issuance on purchase