Documentation
Learn how to use CheckMyLicense with step-by-step guides, API reference, and SDK examples.
User Guide
Get started, understand operations, and configure your workspace.
API Reference
Authenticate, create and manage licenses, and verify activations.
SDKs
Explore MQL5 and Python SDKs with installation, quick start, and examples.
Overview
After logging in, the left‑side navigation divides the site into four main areas:
- Operations – programs and licenses.
- E‑Commerce – products, referees, customers, orders and payments.
- Settings – user profile, organization, billing and balance.
Search boxes and "Add" buttons appear at the top of most pages, making it easy to find items and create new ones.
Important: The menu structure and available features may differ from user to user based on their assigned role (Administrator or Member) and their active subscription package. Administrators have full access to all features, while Members may have restricted access to certain administrative functions.
Operations
Dashboard
The dashboard summarises your organisation's usage. It displays counts of programs, total and active licences, and API hits, along with charts for licence creation and usage. Use it for a quick health check.
Programs
Purpose: Register software programs and their versions so that products and licences can reference them.
- Go to Programs and click Add Program.
- Enter a Program Name and Program Code (unique short identifier). Optionally upload a logo and write a description.
- Click Create. The program appears in the table with actions to view (eye), edit (pencil) or delete (trash). The delete icon removes the program permanently.
- To add a version, click the eye icon, then Add Version. Provide a version number, description and upload the program file. Versions appear beneath the program with actions to edit or delete.
Release Management: A complete release cycle is available through program versions. When you publish a new version, users with active licenses can receive automated email notifications about the update, ensuring they stay informed about the latest releases of your software.
Licences
Purpose: Generate activation keys that allow customers to use your program.
- Navigate to Licences and click Add Licence.
- Select the Program, type the Customer Email, set a Limit (devices allowed, 0 = unlimited) and an Expiry Date (or leave blank for unlimited).
- Click Generate. A unique licence key appears; copy it or send via email. The licence list shows email, program and status (Active, Expired, etc.), with actions to view or edit a licence.
License Management
After creation, only the license status can be edited. Each license maintains its own list of activations, which can be managed based on the Activations Deletable flag. When enabled, users can delete individual activations if needed.
End users can manage their activations directly at www.checkmylicense.online/myactivations. An SDK will be available soon, allowing you to integrate activation management into your own website without redirecting users to the CheckMyLicense platform.
API Integration
CheckMyLicense provides a ready-to-use API for license operations that you can integrate directly with your programs:
- License Creation – programmatically generate licenses
- License Update – modify license status and parameters
- License Check – verify license validity and create activations
For detailed API documentation, parameters, and code examples, please refer to the API section of this help guide.
E‑Commerce
This section enables you to sell programs, reward affiliates, manage customers and handle orders and payments.
Coming Soon: A comprehensive E-Commerce API will be available soon, allowing you to integrate CheckMyLicense with your website as a full-fledged backend solution. This will enable seamless product sales, order management, and payment processing directly through your own website/s.
Products
Products allow you to commercialize your programs in different packages, offering flexibility in how you monetize your software. Each product can have its own configuration including different activation limits, validity periods, and pricing structures. This opens a wide range of possibilities for your commercial imagination—from offering trial versions with limited activations to premium packages with extended validity and unlimited devices.
Create a Product
- Navigate to Products and select Add Product.
- Specify a Title and Product Code, select the Program it belongs to and set the Currency and Price.
- Mark Discountable if referral codes should apply discounts.
- Define Limit (number of licences per purchase, 0 = unlimited) and Validity (days before licence expiry, 0 = no expiry).
- Write a description for storefront display.
- Click Create. The product appears with actions to view, edit or delete.
Referees and Referral Codes
Referees are affiliates who refer customers using referral codes.
Add Referee
- Navigate to Referees and click Add Referee.
- Provide the contact's email, first name and last name.
- Enter phone number, country, state and address.
- Click Save.
- The referee appears in the list with actions to view or edit.
Note: There is no delete option; a referee can only be cancelled (deactivated).
Roadmap: Referee compensation is not currently handled by CheckMyLicense, but it is on our roadmap. We welcome your ideas and feedback on how you would like this feature to work. Please don't hesitate to share your suggestions.
Create Referral Code
Open a referee's detail page and click Add Referral. Enter a unique code and select whether the discount is Percentage Based or Fixed Amount. Provide the discount value, optionally select a Program to limit its scope, and set an Expiry Date (or leave blank for "Never expires").
If Allow Multiple Usage is checked, the code can be redeemed unlimited times; otherwise the usage limit is exactly one. You cannot set custom usage counts beyond one or unlimited.
Customers
To create an order, you must have a customer on file.
- Go to Customers and click Add Customer.
- Enter the Email, First Name, Last Name, Phone, Country, State and Address. Click Create.
- The customer list shows names and emails with actions to view, edit or delete.
Note: Customers can be deleted as long as they don't have any orders associated with them. Once a customer has orders, they can only be deactivated if necessary.
Orders
Order Statuses
- Draft – created but not confirmed. Can be edited or cancelled. Payment cannot be recorded at this stage.
- Pending Payment – order confirmed and awaiting payment. At this status, you may record payments. Once the paid amount equals the order total the status becomes Paid; partial payments change the status to Partial Paid.
- Paid / Partial Paid – payment recorded. A backend job (runs hourly) automatically generates the associated licences. Paid orders can be cancelled within 24 hours; the status then changes to Refunded and another backend job deactivates the licences.
- Cancelled – order cancelled before payment; no licences generated.
Create an Order
- Navigate to Orders and click Create Order. Select a Customer. Optionally choose a Referral Code; the discount value is calculated automatically.
- Click Add Product, select the desired product and set the quantity. You may add multiple products. The order total and discount update in real time.
- Click Create Order. A confirmation modal shows the order code and total. Confirming sets the status to Draft. Open the order details to review.
- On the order details page, use the icons to Edit (pencil), Confirm (blue check) or Cancel (red icon). Confirming moves the order to Pending Payment; cancelling at this stage removes the order. There is no deletion; cancellation simply marks the order as cancelled.
Payments
Payments must correspond to orders that are Pending Payment (not Draft or already Paid). CheckMyLicense provides a payment screen to handle transactions directly through the platform.
Record a Payment
- Go to Payments and click Add Payment.
- Fill in the following details:
- Order Code – e.g.,
ORD251017133003DR1 - Amount – amount paid
- Provider – name of the payment provider (e.g.,
cash,stripe,paypal) - Status – choose from Captured (successful), Failed, etc.
- Payment Method and Provider Transaction ID – optional reference values
- Order Code – e.g.,
- Click Record Payment. If the order status is Pending Payment, the payment is accepted and appears in the payments list. If the order is still Draft or already Paid, the system rejects the payment.
Payments update the Paid Amount field in the order. When total paid equals the order total, the order status changes to Paid. Licences are then generated automatically by the backend job.
Roadmap: A webhook will be available to update payment information directly from your website or platform. Integrations with Xpay, PayPal, and Stripe are planned for future releases, enabling seamless payment processing.
Settings
User Profile
Use User Profile to manage your account:
- Upload or update your Profile Picture.
- Edit your Name, Email and Phone Number.
- Change your password via the Change Password dialog after entering your current password.
For Members: Users who are members of an organization (not administrators) have the option to leave the organization. However, leaving will result in loss of access to the organization's resources unless you are re-invited by an administrator.
Organization
This section contains organisation‑wide settings.
Administrator Role
Each organization has one administrator who has exclusive access to critical functions including:
- Managing billing and subscription plans
- Inviting and managing team members
- Accessing API integration keys
- Modifying organization-wide settings
Members are added to the organization via email invitation. The administrator can delegate their role to another team member through the Change Admin function, but doing so will transfer all administrative privileges to the new administrator and revoke them from the current one.
Organization Settings
- Organization Information: Edit fields such as organisation name, type (Personal or Company), phone, email, country, website, Tax ID and billing address.
- API Keys: Manage your API authentication credentials for license verification operations. You can create up to 3 API keys, each consisting of a unique key (UUID) and secret that are auto-generated for security. Each API key can be:
- Created – click Create API Key and provide a description (e.g., "Production API", "Development API").
- Activated/Deactivated – edit the status to control which keys are currently valid for authentication.
- Deleted – permanently remove keys you no longer need.
- Copied – use the copy buttons to quickly copy the key or secret to your clipboard.
- Hidden/Shown – toggle visibility of the key and secret values for security.
- Members: Click Manage Members to view and manage your team. Each member entry shows name, email, role (ADMIN or MEMBER), status (Active or Suspended), invited and joined dates. Actions allow:
- Invite Member – enter an email address to send an invitation link.
- Change Admin – assign the admin role to another member.
- Suspend – temporarily remove access from a member. Suspended members can be reactivated later.
There are no limits on the number of members you can invite at present.
Billing
The billing page displays your current consumption and estimated overage costs. You see your next billing date and the usage limits for programs, active licences and API hits. Exceeding these limits incurs overage charges depending on your subscription plan. A list of invoices appears once your organisation has billable transactions.
Subscription Plans
CheckMyLicense offers several subscription packages, each designed to meet different business needs. Plans include varying limits for programs, active licenses, and API hits, with options for overage support when you exceed included limits.
Available Plans
- Starter Kit (Free) – Perfect for getting started with basic features and community support.
- Pay as you Go – Flexible usage-based pricing with email support.
- Growth – Includes E‑Commerce support, email support, and overage capability for growing businesses.
- Pro – Enterprise-grade features with multi‑user support, priority support, and white‑labelled emails.
You can view your current subscription via Billing → Manage Subscription, which shows your active plan, billing period and included features. Use Cancel Subscription to stop billing, or upgrade to higher-tier packages as your business grows.
Balance and Add Credit
The Balance page shows your available credit (for overage fees) and a ledger of credit transactions.
Add Credit
- Click Add Credit. Enter the amount and select a payment method:
- Xpay (USDT TRC20) – redirects to the Xpay portal where you can complete the cryptocurrency payment.
- PayPal – redirects to PayPal's SDK for processing.
- Other Payment Method – opens a support form where you may arrange offline payment (e.g., bank transfer, cheque deposit). The operations team manually adds the credit after verifying the payment.
- Check the box agreeing that credits will pay current and future dues.
- Follow the external instructions to finish the payment. On returning, your balance will update.
Typical Workflow
Use the following steps to sell a program to a customer:
- Create a Program and add a version under Operations → Programs.
- Create a Product linked to that program under E‑Commerce → Products.
- (Optional) Add a Referee and Referral Code if you wish to offer discounts.
- Add a Customer under E‑Commerce → Customers.
- Create an Order for the customer in Orders. Select the product, apply any referral code and confirm the order. The order enters Draft status.
- Confirm the Order via the blue check icon on the order page. The status becomes Pending Payment.
- Record Payment in Payments. Provide the order code, amount and provider details. Once payments cover the total, the order status becomes Paid and licences are generated automatically by the backend job (runs hourly).
- Monitor Licences and Overage Usage in Operations → Licences and Settings → Billing.
Orders can be cancelled before payment or within 24 hours after payment (status changes to Refunded). There is no delete function; cancelling is the only way to void an order or referral.
Frequently Asked Questions
Order and Payment Workflow
Payments are only accepted when an order is in Pending Payment status. Attempting to add a payment while the order is still Draft or already Paid results in "Failed to insert payment."
Cancellations
There is no delete function for orders, programmes, products or referrals. You can cancel orders (Draft or Pending Payment) or refund paid orders within 24 hours. Licences are created or deactivated automatically by backend jobs.
Referral Code Usage
If Allow Multiple Usage is unchecked, a referral code may be used once. There is no way to specify a custom usage count. When checked, the code can be used unlimited times.
Add Credit Methods
Xpay and PayPal redirect you to their respective payment portals, while Other Payment Method contacts support for manual payment arrangements (e.g., bank transfer or cheque deposit).
Multi‑User Limits
There are no limits on the number of members you can invite to your organisation.
Authentication
Authenticate with email/password to receive a session object. Use the returned accessToken as a Bearer token in Authorization header for protected endpoints.
Sign In with Email/Password
/signin/email-passwordRequest body: email and password.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
email | string | body | yes | User email |
password | string | body | yes | User password |
POST https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/signin/email-password HTTP/1.1
Content-Type: application/json
{
"email": "john.doe@example.com",
"password": "MyP@ssw0rd!"
}{
"session": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjA2MjEwMjMs...",
"accessTokenExpiresIn": 900,
"refreshToken": "b62f696a-b61b-4f2e-9e74-f4f18c6062d3",
"refreshTokenId": "9fe23c69-da50-4212-b467-0a219eb9b4b1",
"user": {
"id": "693de173-d234-509a-a1dc-4d40da908866",
"email": "john.doe@example.com",
"displayName": "John Doe"
}
}
}Use the access token in subsequent requests:
Authorization: Bearer <accessToken>License Create
Create License
/license/createCreate a new license. Implemented in functions/license/create. The endpoint requires organization context (auth provides organization_id).
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
email | string | body or query | yes | License owner email |
program_name | string | body or query | yes | Program name |
limit | integer | body or query | no | Activation limit |
expiry | bigint | body or query | no | Expiry timestamp (seconds or ms accepted) |
activations_deletable | boolean | body or query | no | Whether activations can be deleted |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/license/create" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"program_name": "My Product",
"limit": 3,
"expiry": 1761315325262
}'Possible Responses
Success
{
"success": {
"message": "License created",
"data": {
"insert_licenses": {
"affected_rows": 1,
"returning": [{
"id": 309,
"email": "example@tradet.net",
"license": "9845cca98bbe4664bbbc9fb10862a453",
"program_id": 42,
"limit": 10,
"expiry": null,
"status": 0,
"activations_deletable": true,
"organization_id": "723493dd-4a3c-cd4d-8b7b-c288704d96e9",
"created_at": "2025-10-17T09:08:59.838129+00:00",
"updated_at": "2025-10-17T09:08:59.838129+00:00"
}]
},
"subscription_info": {
"subscription_id": 70,
"package_id": 36,
"overage_created": false,
"is_vip": false,
"current_license_count": 5,
"license_limit": 10,
"reason": "Within package limits"
}
}
}
}Missing or invalid parameters
{
"error": {
"message": "Invalid license request",
"errors": ["email is required", "program_name not found"]
}
}Caller is not associated with an organization
{
"error": {
"message": "User must belong to an organization to create licenses"
}
}Program not owned by organization or not found
{
"error": {
"message": "Program not found or not owned by this organization"
}
}Subscription does not allow creating more licenses
{
"error": {
"message": "Subscription limits prevent creating new licenses",
"reason": "license limit reached for current package"
}
}General failure while processing the request
{
"error": {
"message": "Error creating license",
"data": { "message": "<error object>", "stack": "..." }
}
}License Update
Update License
/license/updateUpdate license fields such as status. Implemented in functions/license/update. The handler expects license_id and any fields to set.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
license_id | integer | query or body | yes | License ID to update |
status | integer | query or body | no | 0=active, 1=inactive (will set deactivated_at) |
curl -X PUT "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/license/update" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "license_id": 124, "status": 1 }'{
"success": {
"message": "License updated: status and deactivated_at updated",
"data": {
"update_licenses": {
"affected_rows": 1,
"returning": [{
"id": 309,
"status": 1,
"deactivated_at": "2025-10-17T09:23:23.35+00:00",
"updated_at": "2025-10-17T09:23:23.38114+00:00"
}]
}
}
}
}License Check
Verify License
/license/verifyVerify a license and create an activation record using HMAC-SHA256 signature authentication. Implemented in functions/license/verify.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
api_id | string (UUID) | body | yes | Organization ID (from Settings → Organization) |
ts | string | body | yes | Current Unix timestamp in seconds (UTC) |
payload | string (base64) | body | yes | Base64-encoded JSON payload with license, email, activation details, and program name. The server will verify the provided program matches the license's program and will return 400 invalid program on mismatch. |
sig | string (base64) | body | yes | HMAC-SHA256 signature of canonical string (base64 encoded) |
v | string | body | no | API version (default: "2") |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/license/verify" \
-H "Content-Type: application/json" \
-d '{
"api_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"ts": "1729875123",
"payload": "BASE64_ENCODED_PAYLOAD",
"sig": "BASE64_HMAC_SIGNATURE",
"v": "2"
}'Payload Structure (JSON, before base64 encoding)
{
"license": "9845cca98bbe4664bbbc9fb10862a453",
"email": "john@example.com",
"target_id": "123456789",
"target_host": "MT5-PC\\Server",
"type": "Live",
"program": "My Product"
}Field Descriptions:
license: The full 32-char hex license keyemail: License owner's email address (must match the license, comparison is case-insensitive)target_id: Account number or unique machine identifiertarget_host: Server name or hostnametype: Account type (e.g., "Live", "Demo")program: Program name (must match the license's program)
Limits and Expiry:
limit === 0: Means unlimited activations. The server checks prospective usage as used + 1 against the limit.expiry: The license expiry in the system may be stored as seconds or milliseconds. The server handles both; response validity will match the stored value. Clients should treat validity as a timestamp (ms or large number).
Possible Responses
| Status | Message | Description |
|---|---|---|
200 | activation exists | License verified successfully. This target already has an active license. |
200 | activation created | First time this target is checking this license. New activation was created. |
400 | missing required fields | One or more required fields (api_id, ts, payload, sig) are missing or empty. |
401 | stale timestamp | Timestamp is too old or too far in the future (must be within ±60 seconds of server time). |
401 | invalid api_id | API Key (api_id) not found or inactive in the system. |
401 | invalid signature | HMAC-SHA256 signature verification failed. Verify your api_secret and signature computation. |
400 | invalid payload | Payload cannot be decoded from base64 or is not valid JSON. |
404 | unmatched license | No license matching the provided UUID exists for this organization. |
400 | license inactive | License status is 1 (inactive/deactivated). Cannot use inactive licenses. |
400 | invalid email | Provided email does not match the license owner email. |
400 | expired certificate | License expiry timestamp has passed. License has expired. |
400 | max limit reached | Maximum number of activations for this license has been exceeded. |
500 | internal error | Internal server error while processing the verification request. |
Activation Tracking
- An organization-level 'hit' is created immediately after org verification. If the license is found, the hit is updated with
license_idand the license'slast_used_atis updated. - Activation insertion is best-effort; verification succeeds even if activation DB insert fails.
- If an activation with matching
target_id,target_host, andtypealready exists, the server returns 200 with message: 'activation exists'.
Encoding & Encryption
The license verify endpoint uses HMAC-SHA256 signature authentication. The request body is JSON, but the payload field must be base64-encoded and the request is authenticated with an HMAC-SHA256 signature.
Request Signature Flow
To verify a license, follow these steps:
- Prepare JSON payload with license, email, and target info
- Base64 encode the JSON payload
- Get current Unix timestamp (seconds, UTC)
- Build canonical string exactly as:
api_id.ts.payload_base64— wherepayload_base64is the literal base64 string you include in the payload field. Do NOT decode or alter the base64 string when creating the canonical string. - Compute HMAC-SHA256 of canonical string using your
api_secret - Base64 encode the HMAC result
- Send JSON request with api_id, ts, payload, sig, and v fields
Building the Payload
The JSON payload contains the license and activation information:
{
"license": "9845cca98bbe4664bbbc9fb10862a453",
"email": "john@example.com",
"target_id": "123456789",
"target_host": "MT5-Desktop\\EURUSD",
"type": "Live",
"program": "My Product"
}Payload Fields:
license: 32-character hex license keyemail: Email address of the license ownertarget_id: Account login number or unique machine IDtarget_host: Server hostname or terminal identifiertype: Account type (e.g., "Demo", "Live")program: Program name (must match the license's program)
eyJsaWNlbnNlIjoiOTg0NWNjYTk4YmJlNDY2NGJiYmM5ZmIxMDg2MmE0NTMiLCJlbWFpbCI6ImpvaG5AZXhhbXBsZS5jb20iLCJ0YXJnZXRfaWQiOiIxMjM0NTY3ODkiLCJ0YXJnZXRfaG9zdCI6Ik1UNS1EZXNrdG9wXFxFVVJVU0QiLCJ0eXBlIjoiTGl2ZSIsInByb2dyYW0iOiJNeSBQcm9kdWN0In0=Input: api_id = "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
ts = "1729875123"
payload_base64 = "eyJsaWNlbnNlIjoiOTg0NWNjYTk4YmJlNDY2NGJiYmM5ZmIxMDg2MmE0NTMiLCJlbWFpbCI6ImpvaG5AZXhhbXBsZS5jb20iLCJ0YXJnZXRfaWQiOiIxMjM0NTY3ODkiLCJ0YXJnZXRfaG9zdCI6Ik1UNS1EZXNrdG9wXFxcXEVVUlVTRCIsInR5cGUiOiJMaXZlIiwicHJvZ3JhbSI6Ik15IFByb2R1Y3QifQ=="
Output: "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d.1729875123.eyJsaWNlbnNlIjoiOTg0NWNjYTk4YmJlNDY2NGJiYmM5ZmIxMDg2MmE0NTMiLCJlbWFpbCI6ImpvaG5AZXhhbXBsZS5jb20iLCJ0YXJnZXRfaWQiOiIxMjM0NTY3ODkiLCJ0YXJnZXRfaG9zdCI6Ik1UNS1EZXNrdG9wXFxcXEVVUlVTRCIsInR5cGUiOiJMaXZlIiwicHJvZ3JhbSI6Ik15IFByb2R1Y3QifQ=="Computing HMAC-SHA256 Signature
Use your api_secret (from Settings → Organization) and the canonical string to compute an HMAC-SHA256 digest, then base64-encode it.
Steps:
- Take the canonical string:
api_id.ts.payload_base64 - Compute HMAC-SHA256 using your
api_secretas the key and the canonical string as the data - Base64 encode the resulting HMAC digest
- Use this base64-encoded value as the
sigparameter
Most modern programming languages have built-in libraries for HMAC-SHA256 computation. Look for crypto, hashlib, or similar security libraries in your language of choice.
Timestamp Window: Default ±60 seconds (server uses env LICENSE_VERIFY_TS_WINDOW to override). Ensure your system clock is synchronized.
API Secret Lookup: Server fetches the organization secret from the database (Settings → Organization). For local/testing, a fallback API_SECRETS_JSON env var can provide {"apiId": "secret"} mappings — internal/testing only.
Response Signing
On successful responses, the server adds server_sig and server_ts. The server computes server_sig as base64(HMAC-SHA256) over the canonical response string:
${api_id}.${server_ts}.${success}.${message}.${base64(JSON.stringify(data))}If data is empty, the last segment is an empty string. server_ts is the server unix time in seconds (string). Clients can verify server_sig with the organization's api_secret.
Input: api_id = "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
server_ts = "1729875130"
success = "true"
message = "activation exists"
data = {"validity":1761315325262,"limit":3,"used":1}
Output: "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d.1729875130.true.activation exists.eyJ2YWxpZGl0eSI6MTc2MTMxNTMyNTI2MiwibGltaXQiOjMsInVzZWQiOjF9"Complete Request Example
{
"api_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"ts": "1729875123",
"payload": "eyJsaWNlbnNlIjoiOTg0NWNjYTk4YmJlNDY2NGJiYmM5ZmIxMDg2MmE0NTMiLCJlbWFpbCI6ImpvaG5AZXhhbXBsZS5jb20iLCJ0YXJnZXRfaWQiOiIxMjM0NTY3ODkiLCJ0YXJnZXRfaG9zdCI6Ik1UNS1EZXNrdG9wXFxcXEVVUlVTRCIsInR5cGUiOiJMaXZlIiwicHJvZ3JhbSI6Ik15IFByb2R1Y3QifQ==",
"sig": "BASE64_HMAC_SIGNATURE",
"v": "2"
}Breaking it down:
api_id= Organization IDts= Current Unix timestamp (seconds)payload= Base64(JSON payload with program field)sig= Base64(HMAC-SHA256(api_id.ts.payload, api_secret))v= API version (default: "2")
Response Handling
The response is always JSON. Check the HTTP status code:
- 200 OK — License verified. Response contains
success: true,message, anddatawith validity/limit/used. - 400-500 — Error. Response contains
errorobject withcodeandmessage.
{
"success": true,
"message": "activation exists",
"data": {
"validity": 1761315325262,
"limit": 3,
"used": 1,
"offline_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // optional - best-effort
"offline_expires_at": 1761910123 // unix seconds
},
"server_sig": "BASE64_HMAC_SIGNATURE",
"server_ts": "1729875130"
}Response Data Fields:
validity: License expiry timestamp (may be in seconds or milliseconds)limit: Maximum number of activations allowed (0 = unlimited)used: Current number of activationsoffline_token: Base64 JSON token (optional — generated best-effort)offline_expires_at: Unix seconds expiry for the offline token (optional)
Note: Clients should accept responses that omit offline_token.
{
"error": {
"code": 401,
"message": "invalid signature"
}
}Signature Verification Failed?
- Verify your
api_secretis correct - Ensure timestamp is recent (within ±60 seconds of server time, configurable via
LICENSE_VERIFY_TS_WINDOW) - Confirm canonical string format:
api_id.ts.payload_base64(with periods, no spaces) - Use the literal base64 string from the payload field — do NOT decode or alter it when building the canonical string
- Check that HMAC output is base64-encoded for the
sigfield
Create Customer
Create Customer
/order/customer/createCreates a new customer record within the organization. Validates email format and uniqueness, phone number format (if provided), and enforces required fields. Customers are scoped to the authenticated user's organization.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
customer.email | string | body | yes | Customer email address. Must be valid format and unique within organization. |
customer.first_name | string | body | yes | Customer first name. Cannot be empty. |
customer.last_name | string | body | yes | Customer last name. Cannot be empty. |
customer.country | string | body | no | Country name or code. |
customer.state | string | body | no | State, province, or region. |
customer.address | string | body | no | Full street address. |
customer.phone | string | body | no | Phone number (international format supported). Must have at least 7 digits. |
customer.referee | boolean | body | no | Whether customer is a referee. Defaults to false. |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/customer/create" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"country": "United States",
"state": "California",
"address": "123 Main Street, Apt 4B",
"phone": "+1 (555) 123-4567",
"referee": false
}
}'Validation Rules
- email: Must match regex
/^[^\s@]+@[^\s@]+\.[^\s@]+$/ - first_name: Cannot be empty after trimming
- last_name: Cannot be empty after trimming
- phone: Must contain at least 7 digits; allows
+,-, spaces, parentheses
Possible Responses
Customer created and returned with ID
{
"success": true,
"data": {
"customer": {
"id": 42,
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"country": "United States",
"state": "California",
"address": "123 Main Street, Apt 4B",
"phone": "+1 (555) 123-4567",
"referee": false,
"organization_id": "6e8a2322-37de-4b91-8a0e-67ebd3873643",
"user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2025-12-29T12:00:00.000Z"
}
}
}Missing required fields, invalid email format, invalid phone format, or duplicate email
{
"error": {
"code": 400,
"message": "Invalid email format"
}
}Missing or invalid JWT token
{
"error": {
"code": 401,
"message": "Authentication required"
}
}User lacks organization membership
{
"error": {
"code": 403,
"message": "Organization membership required"
}
}Email already exists in organization
{
"error": {
"code": 409,
"message": "Customer with this email already exists"
}
}Notes:
- Email is case-sensitive and stored as-provided
- Phone validation allows international formats
- All string fields are automatically trimmed
user_idandorganization_idare automatically set from JWT context
Submit Order
Submit Order
/order/submitCreates a new order with line items, calculates pricing with optional referral discounts, and supports both single-use and multiple-use referral codes. Automatically consolidates duplicate product entries and validates product availability within the organization.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
order.code | string | body | no | Custom order code. Auto-generated if omitted. |
order.customer_id | integer | body | yes | ID of the customer placing the order. Must belong to organization. |
order.referral_code | string | body | no | Referral/discount code to apply. |
order.items | array | body | yes | Array of line items with product_id and qty. |
order.items[].product_id | integer | body | yes | Product ID. Must belong to organization. |
order.items[].qty | integer | body | yes | Quantity (positive integer). Duplicates are consolidated. |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/submit" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"order": {
"code": "ORD2512290001ABC",
"customer_id": 42,
"referral_code": "WELCOME10",
"items": [
{ "product_id": 15, "qty": 2 },
{ "product_id": 18, "qty": 1 }
]
}
}'Warning Messages
The warnings array may include:
"Referral code not found"- Provided referral code doesn't exist"Referral expired"- Referral code has passed expiry date (attached but no discount applied)"Referral already used"- Single-use referral already redeemed (attached but no discount applied)"Referral scoped but no programs linked"- Scoped referral has no eligible programs configured
Possible Responses
Order created with line items and optional discount applied
{
"success": true,
"warnings": [],
"data": {
"referral_applied": true,
"order": {
"id": 114,
"code": "ORD2512290001ABC",
"status": 0,
"currency": "USD",
"original_amount": 59.97,
"discount_amount": 5.99,
"final_amount": 53.98,
"customer_id": 42,
"referral_id": 7,
"created_at": "2025-12-29T10:30:15.123Z",
"order_items": [...]
}
}
}Invalid customer_id, missing items, invalid product prices, or duplicate order code
{
"error": {
"code": 400,
"message": "Customer not found in organization"
}
}Customer or products not found in organization
{
"error": {
"code": 404,
"message": "Product not found"
}
}Update Order
Update Order
/order/updateUpdates an existing draft order (status = 0) with new line items and/or referral code. Recalculates pricing, replaces all items, and manages referral usage counters. Only the order owner can update their orders.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
order.id | integer | body | yes | ID of the order to update. Must be in draft status (0). |
order.code | string | body | no | New order code (optional). Must be unique within organization. |
order.referral_code | string | body | no | New referral code. null removes existing referral. |
order.items | array | body | yes | Complete replacement item list. Previous items are deleted. |
order.items[].product_id | integer | body | yes | Product ID. Must belong to organization. |
order.items[].qty | integer | body | yes | Quantity (positive integer). |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/update" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"order": {
"id": 114,
"code": "ORD2512290001XYZ",
"referral_code": "SAVE20",
"items": [
{ "product_id": 15, "qty": 5 },
{ "product_id": 20, "qty": 2 }
]
}
}'Possible Responses
Order updated with new items and/or referral
{
"success": true,
"warnings": [],
"data": {
"referral_applied": true,
"order": {
"id": 114,
"code": "ORD2512290001XYZ",
"status": 0,
"original_amount": 124.93,
"discount_amount": 24.99,
"final_amount": 99.94,
"referral_id": 12,
"updated_at": "2025-12-29T11:45:22.456Z",
"order_items": [...]
}
}
}Invalid order id, non-draft status, missing items, duplicate code, or invalid products
{
"error": {
"code": 400,
"message": "Order is not in draft status"
}
}Order not owned by current user
{
"error": {
"code": 403,
"message": "Not authorized to update this order"
}
}Notes:
- Previous referral usage is decremented if a discount was applied
- New referral usage is incremented only if new discount is applied
- All previous order_items and referral_redemptions are deleted and recreated
updated_attimestamp is automatically set
Confirm Order
Confirm Order
/order/confirmTransitions a draft order (status = 0) to pending payment (status = 1). This is typically the first step after order submission before payment processing. Only draft orders can be confirmed.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
order_id | integer | body | yes | ID of the order to confirm. Must be in draft status (0). |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/confirm" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"order_id": 114
}'curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/confirm" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"order": {
"id": 114
}
}'Order Status Reference
| Status | Label | Description |
|---|---|---|
0 | Draft | Order created but not confirmed |
1 | Pending Payment | Order confirmed, awaiting payment |
2 | Partially Paid | Some payment received |
3 | Paid | Fully paid |
4 | Cancelled | Order cancelled |
5 | Refunded | Payment refunded |
6 | Partially Refunded | Partial refund issued |
Possible Responses
Order status changed to pending payment (1)
{
"success": true,
"data": {
"order": {
"id": 114,
"code": "ORD2512290001ABC",
"status": 1,
"updated_at": "2025-12-29T10:35:42.789Z"
}
}
}Invalid order_id or order not in draft status
{
"error": {
"code": 400,
"message": "Order is not in draft status"
}
}Order not found in organization
{
"error": {
"code": 404,
"message": "Order not found"
}
}Cancel Order
Cancel Order
/order/cancelCancels an order with optional refund processing and license deactivation. Supports flexible cancellation scenarios: simple status change, refund creation (full or partial), and asynchronous license deactivation via job queue. Idempotent for already-cancelled orders.
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
order_id | integer | body | yes | ID of the order to cancel. |
refund | object | body | no | Refund configuration. Omit for no refund. |
refund.enabled | boolean | body | no | Set to true to create refund payment. |
refund.amount | number | body | no | Specific refund amount. Omit for full auto-calculated refund. |
deactivate_licenses | boolean | body | no | Set to true to queue license deactivation job. |
notes | string | body | no | Cancellation notes (stored in order.notes). |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/cancel" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"order_id": 114,
"notes": "Customer requested cancellation"
}'curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/cancel" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"order_id": 114,
"refund": {
"enabled": true,
"amount": 25.00
},
"deactivate_licenses": true,
"notes": "Partial refund with license revocation"
}'Cancellation Scenarios
| Scenario | refund.enabled | deactivate_licenses | Behavior |
|---|---|---|---|
| Simple Cancel | false or omitted | false or omitted | Sets status to 4, updates canceled_at |
| Cancel with Full Refund | true, no amount | false or omitted | Cancels + creates refund for all captured funds |
| Cancel with Partial Refund | true, with amount | false or omitted | Cancels + creates refund for specified amount |
| Cancel with License Revocation | false or omitted | true | Cancels + queues license deactivation job |
| Full Cancel Package | true | true | Cancels + refunds + queues license deactivation |
Possible Responses
Order cancelled (with optional refund/deactivation)
{
"success": true,
"data": {
"order": {
"id": 114,
"status": 4,
"canceled_at": "2025-12-29T13:00:00.000Z",
"notes": "Customer requested cancellation"
},
"actions_taken": {
"cancelled": true,
"refund_created": false,
"refund_amount": 0,
"license_job_id": null
}
}
}Full cancellation package
{
"success": true,
"data": {
"order": {
"id": 114,
"status": 4,
"notes": "Partial refund with license revocation"
},
"refund": {
"id": 88,
"amount": 25.00,
"status": 3,
"provider_txn_id": "REFUND-114-1735471215789"
},
"actions_taken": {
"cancelled": true,
"refund_created": true,
"refund_amount": 25.00,
"license_job_id": 456
}
}
}Invalid order_id, refund amount exceeds available funds, or refund amount exceeds order total
{
"error": {
"code": 400,
"message": "Refund amount exceeds order total"
}
}Notes:
- Refund amount validation: must not exceed
order.final_amountand available captured funds - License deactivation is asynchronous (job-based) and non-blocking
- Multiple cancellations are idempotent when no actions are requested
canceled_attimestamp is set on first cancellation only
Record Payment
Record Payment
/order/paymentRecords a payment transaction for an order identified by order code. Supports captured payments (status = 1), failed payments (status = 2), and refunds (status = 3). Automatically updates order status when fully paid and optionally enqueues license creation jobs for captured payments.
Can be called as an authenticated API request or as a webhook (with relaxed authentication).
| Parameter | Type | In | Required | Description |
|---|---|---|---|---|
payment.order_code | string | body | yes | Order code to apply payment to. |
payment.provider | string | body | yes | Payment provider name (e.g., "stripe", "paypal", "manual"). |
payment.provider_txn_id | string | body | yes | Unique transaction ID from payment provider. |
payment.amount | number/string | body | yes | Payment amount (positive number). Accepts numeric strings. |
payment.status | integer | body | yes | Payment status: 1 = captured, 2 = failed, 3 = refund. |
payment.payment_method | string | body | no | Payment method type (e.g., "card", "bank_transfer"). |
payment.external_reference | string | body | no | Additional reference ID (e.g., customer ID, invoice number). |
curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/payment" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"payment": {
"order_code": "ORD2512290001ABC",
"provider": "stripe",
"provider_txn_id": "pi_3AB12CD34EF56GH78",
"amount": 53.98,
"status": 1,
"payment_method": "card",
"external_reference": "cus_ABC123XYZ"
}
}'curl -X POST "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/order/payment" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"payment": {
"order_code": "ORD2512290001ABC",
"provider": "stripe",
"provider_txn_id": "re_3ZY98WX76VU54TS32",
"amount": 25.00,
"status": 3,
"external_reference": "refund-20251229"
}
}'Payment Status Reference
| Status | Label | Description | Effect on Order |
|---|---|---|---|
1 | Captured | Payment successfully received | Adds to captured total; updates order status when fully paid |
2 | Failed | Payment attempt failed | Recorded for audit; no effect on order status |
3 | Refund | Money returned to customer | Recorded; does NOT reduce captured total per spec |
Order Status Updates
| Scenario | Order Status | Description |
|---|---|---|
| First captured payment < final_amount | 2 (Partially Paid) | Some payment received |
| Captured total >= final_amount | 3 (Paid) | Fully paid; paid_at timestamp set |
| Failed payment | No change | Status remains as-is |
| Refund payment | No change | Use /order/cancel with refund for status updates |
Possible Responses
Payment captured and order updated
{
"success": true,
"data": {
"payment": {
"id": 234,
"order_id": 114,
"provider": "stripe",
"provider_txn_id": "pi_3AB12CD34EF56GH78",
"amount": 53.98,
"status": 1,
"payment_method": "card",
"received_at": "2025-12-29T12:30:45.123Z"
},
"order": {
"id": 114,
"status": 3,
"captured_total": 53.98,
"paid_at": "2025-12-29T12:30:45.123Z",
"final_amount": 53.98
},
"license_job": {
"id": 789,
"type": "create_licenses",
"status": "pending"
}
}
}Partial payment recorded, order not yet fully paid
{
"success": true,
"data": {
"payment": {
"id": 235,
"amount": 25.00,
"status": 1,
"received_at": "2025-12-29T13:00:00.000Z"
},
"order": {
"id": 114,
"status": 2,
"captured_total": 25.00,
"final_amount": 53.98
}
}
}Missing required fields, invalid status, invalid amount, or duplicate provider_txn_id
{
"error": {
"code": 400,
"message": "Invalid payment status"
}
}Order code not found in organization
{
"error": {
"code": 404,
"message": "Order not found"
}
}Duplicate provider_txn_id already exists
{
"error": {
"code": 409,
"message": "Payment with this transaction ID already exists"
}
}License Creation Jobs
When a captured payment (status = 1) brings the order to fully paid status, a background job is automatically enqueued:
{
"queue": "default",
"type": "create_licenses",
"payload": {
"order_id": 114,
"customer_email": "john.doe@example.com"
},
"dedupe_key": "order:114:create_licenses",
"priority": 100,
"max_attempts": 5
}Notes:
provider_txn_idmust be unique across all payments (prevents duplicate processing)- Amount is stored as decimal with 2 decimal places
- Webhook mode: relaxed authentication for payment gateway callbacks
- Failed payments (status = 2) are recorded but don't affect order totals
- Refund status (3) records the transaction but use
/order/cancelendpoint for full refund workflow
SDK Overview
CheckMyLicense provides Software Development Kits (SDKs) to simplify the integration of license verification into your applications. These SDKs handle all the complexity of encryption, API communication, and response parsing, allowing you to implement robust license protection with just a few lines of code.
Key Features
- Easy Integration – Add license verification with minimal code changes
- Secure Encryption – HMAC-SHA256 encryption for data signatures
- Detailed Error Handling – Clear error messages for troubleshooting
Available SDKs
To download the available SDKs, please visit your organization page: Available SDK section.
- MetaTrader SDK – Add license verification with minimal code changes in MetaTrader 4 or 5 EA and indicators.
- Python SDK – Command-line tool and library for Python applications with built-in HMAC-SHA256 authentication.
More SDKs Coming Soon: We're actively developing SDKs for additional platforms including JavaScript/Node.js, C#/.NET, and Java. If you need an SDK for a specific platform, please contact our support team.
MQL5
The MQL5 SDK enables seamless license verification for MetaTrader 5 Expert Advisors, Indicators, and Scripts. Protect your trading tools with enterprise-grade license management.
Installation
Important: Always enable Cloud Protect and rebuild regularly
When compiling your Indicator or Expert Advisor (EA), always enable Cloud Protect. Using cloud protection adds an extra layer of security for your published EA and helps prevent unauthorized redistribution. Never share an EA without cloud protection enabled.
- Enable Cloud Protect every time you compile your indicator or EA.
- Never distribute an unprotected EA — always use cloud protection for published builds.
- Periodically rebuild your programs using newer MetaTrader builds and SDK versions to benefit from security fixes and compiler improvements.
Prerequisites
- MetaTrader platform installed
- MetaEditor installed
- Active CheckMyLicense organization account
- Your organization's API Key and Secret (found in Settings → Organization)
Installation Steps
- Download the SDK
- Extract Files
The SDK package contains two files:
CheckMyLicense.mqh– The reusable SDK librarySHA26.mqh– The reusable component for SHA-256 hashingCheckMyLicense.mq5– Example implementation script
- Copy to MetaTrader Directory
Copy
CheckMyLicense.mqhandSHA26.mqhto your MetaTrader 5 Include folder:C:\Users\YourUsername\AppData\Roaming\MetaQuotes\Terminal\[TerminalID]\MQL5\Include\ - Allow WebRequest URL
In MetaTrader 5, go to Tools → Options → Expert Advisors and add the CheckMyLicense API URL to the list of allowed URLs:
https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1
Important: Without adding the URL to allowed WebRequest addresses, the SDK will not be able to communicate with CheckMyLicense servers, and license verification will fail.
Quick Start
Basic Implementation
Add license verification to your Expert Advisor, Indicator, or Script:
1. Define Input Parameters
input string InpEmail = ""; // User's email
input string InpLicence = ""; // License key2. Verify License OnInit for Expert Advisors
int OnInit()
{
if(!CheckMyLicense(InpEmail, InpLicence))
{
Print("License verification failed!");
return INIT_FAILED;
}
Print("License verified successfully!");
return INIT_SUCCEEDED;
}3. Verify License OnStart for Scripts
void OnStart()
{
if(!CheckMyLicense(InpEmail, InpLicence))
{
Print("License verification failed!");
return;
}
// Your script logic here
Print("Running licensed script...");
}Examples
Complete Expert Advisor Example
Here's a complete example of an Expert Advisor with license verification:
//+------------------------------------------------------------------+
//| LicensedEA.mq5 |
//| Copyright 2025, Your Company |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Your Company"
#property link "https://www.yourwebsite.com"
#property version "1.00"
string name = "Your Program Name"
#include "CheckMyLicense.mqh"
// Input parameters
input string InpEmail = ""; // Email
input string InpLicence = ""; // License Key
input double InpLotSize = 0.01; // Lot Size
input int InpMagicNumber = 12345; // Magic Number
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Verify license first
if(!CheckMyLicense(InpEmail, InpLicence))
{
Alert("License verification failed! EA will not run.");
return INIT_FAILED;
}
Print("License verified! EA starting...");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Your trading logic here
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("EA stopped. Reason: ", reason);
}Indicator Example
Important Limitation: MetaTrader does not allow Indicators to call WebRequest() functions for security reasons. If you need to license a custom indicator, you must create a wrapper Expert Advisor that verifies the license and then calls the indicator logic. Alternatively, you can distribute your indicator as part of a licensed EA template.
If you must use an Indicator with license verification, create an EA wrapper that loads the indicator as an embedded resource. This approach requires only license verification on the EA, not the indicator itself:
//+------------------------------------------------------------------+
//| CustomIndicatorWrapper.mq5 |
//| EA that attaches a custom licensed indicator to the chart |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Your Company"
#property link "https://www.yourwebsite.com"
#property version "1.0"
string name = "Licensed Indicator Wrapper"
#include "CheckMyLicense.mqh"
// Embed your custom indicator as a resource
#resource "CustomIndicator.ex5"
// Resource reference for iCustom
#define CUSTOM_INDICATOR "::CustomIndicator.ex5"
// Mirror indicator inputs to match your custom indicator parameters
input int InpPeriod = 14; // MA Period
input int InpShift = 0; // Shift
input bool InpShowSignal = true; // Show Signal Line
// License input
input string InpEmail = "";
input string InpLicence = "";
// Internal
int g_ind_handle = INVALID_HANDLE;
int ext_reason = 0;
//+------------------------------------------------------------------+
//| Helper: Remove indicator from chart if present |
//+------------------------------------------------------------------+
void RemoveIndicator()
{
if(g_ind_handle != INVALID_HANDLE)
{
// Find and remove any indicator with matching name
int total = (int)ChartIndicatorsTotal(0, 0);
for(int i = total - 1; i >= 0; --i)
{
string name = ChartIndicatorName(0, 0, i);
if(StringFind(name, "CustomIndicator") >= 0)
ChartIndicatorDelete(0, 0, name);
}
IndicatorRelease(g_ind_handle);
g_ind_handle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Verify license first
if(ext_reason != 3) // Skip on EA reload (reason 3)
{
if(!CheckMyLicense(InpEmail, InpLicence))
{
Alert("License verification failed! EA will not run.");
return INIT_PARAMETERS_INCORRECT;
}
}
// Ensure any previous instance is removed
RemoveIndicator();
// Create indicator handle with your custom parameters
g_ind_handle = iCustom(
_Symbol,
_Period,
CUSTOM_INDICATOR,
InpPeriod,
InpShift,
InpShowSignal
);
if(g_ind_handle == INVALID_HANDLE)
{
PrintFormat("Failed to create indicator handle. Error=%d", GetLastError());
return INIT_FAILED;
}
// Add indicator to chart
if(!ChartIndicatorAdd(0, 0, g_ind_handle))
{
PrintFormat("ChartIndicatorAdd failed. Error=%d", GetLastError());
IndicatorRelease(g_ind_handle);
g_ind_handle = INVALID_HANDLE;
return INIT_FAILED;
}
Print("License verified! Custom indicator attached to chart.");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
RemoveIndicator();
if(reason == 3)
{
ext_reason = 3; // Track EA reload
}
}
//+------------------------------------------------------------------+
//| Expert tick function (no trading logic needed) |
//+------------------------------------------------------------------+
void OnTick()
{
// Wrapper runs silently; indicator handles all logic
}
Understanding Error Codes
The SDK uses specific error codes to help diagnose issues during license verification:
| Error Code | Description | Solution |
|---|---|---|
EX1001 | Email not provided | Enter your email address in the input parameters (InpEmail) |
EX1002 | License key not provided | Enter your license key in the input parameters (InpLicence) |
EX1003 | Program name not configured | Set #define ProgramName to match your program name in CheckMyLicense |
EX1004 | License validation failed at startup | Verify your license key format and API credentials |
EX2001 | Payload encoding failed | Verify your #define APISecret is correctly set from Organization settings |
EX2002 | HMAC-SHA256 signature computation failed | Verify #define APIID and #define APISecret are correct |
EX3001 | WebRequest URL not allowed in MT5 | Add https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1 to Tools → Options → Expert Advisors → Allow WebRequest for URLs |
EX3003 | Connection error or timeout | Check your internet connection; if persists, verify the server URL is accessible |
EX4001-EX4005 | Cryptographic operation failed (Base64 encoding/decoding) | Verify the license key is valid; check that SHA256.mqh is included in your project |
400 Bad Request | Invalid payload or malformed request | Verify email format is correct and license key contains valid 32 hex characters |
401 Unauthorized | License invalid, expired, or not owned by email | Verify the email matches the license owner; check license hasn't expired in your dashboard |
404 Not Found | License or program not found | Verify the license key exists and the program name matches your CheckMyLicense dashboard |
500 Server Error | CheckMyLicense server error | Try again later; if error persists, contact support |
Testing Your Implementation
- Compile – Compile your MQL5 file in MetaEditor (F7)
- Attach to Chart – Drag your EA/Indicator to a chart
- Enter Credentials – Input your email and license key in the parameters dialog
- Check Logs – Open the Experts tab to see verification results
- Verify Activation – Check your CheckMyLicense dashboard for the new activation
Best Practice: Always verify the license during initialization (OnInit). This prevents unauthorized use and ensures proper activation tracking from the start. Also consider implementing periodic re-validation during runtime for long-running applications.
Python
The Python SDK provides a command-line tool and library for integrating CheckMyLicense verification into Python applications. It handles HMAC-SHA256 authentication, request signing, and server response verification automatically.
Key Features
- Command-Line Interface – Verify licenses directly from terminal or scripts
- Server Signature Verification – Built-in validation of server responses
- Debug Mode – Detailed logging for troubleshooting and development
- Flexible Integration – Use as CLI tool or import as Python module
Installation
Prerequisites
- Python 3.7 or higher
- Active CheckMyLicense organization account
- Your organization's API ID and API Secret (from Settings → Organization)
Installation Steps
- Download the SDK
Download the Python SDK from your organization page (Available SDK section).
- Extract the File
The SDK package contains a single file:
CheckMyLicenseOnline.py– The CLI tool and library
- Install Dependencies
The SDK uses Python standard library only, no external dependencies required.
- Verify Installation
python CheckMyLicenseOnline.py --help
Quick Start
Basic Usage
Verify a license using the command-line interface:
python CheckMyLicenseOnline.py \
--api-id a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d \
--api-secret YOUR_API_SECRET \
--endpoint "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/license/verify" \
--verify-server-sig \
--v 2 \
--program "My Product" \
--license abcd1234ef567890abcd1234ef567890 \
--email user@example.comCommand-Line Parameters
| Parameter | Required | Description |
|---|---|---|
--api-id | Yes | Organization API ID from Settings → Organization |
--api-secret | Yes | Organization API Secret from Settings → Organization |
--endpoint | Yes | CheckMyLicense API endpoint URL |
--license | Yes | 32-character hex license key |
--email | Yes | License owner's email address |
--program | Yes | Program name (must match license's program) |
--v | No | API version (default: 2) |
--verify-server-sig | No | Enable server signature verification (recommended) |
--debug | No | Enable detailed debug output |
--show-body | No | Display full request body (includes sensitive data) |
Example Output
Request body prepared (values hidden for security):
api_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
ts: 1765780072
payload: <base64 payload hidden>
sig: <signature hidden>
Server HTTP status: 200
Response JSON:
{
"success": {
"success": true,
"message": "activation exists",
"data": {
"validity": null,
"limit": 5,
"used": 2,
"offline_token": "eyJwcm9ncmFtX25hbWUi...",
"offline_expires_at": 1766384873
},
"server_sig": "TwONnZyr0t3hvLnuOIkiomlnYrFWQs+zGuf1bEj34io=",
"server_ts": "1765780073"
}
}
Server signature verified OKExamples
Basic License Verification
Standard license verification with server signature validation:
python CheckMyLicenseOnline.py \
--api-id a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d \
--api-secret YOUR_API_SECRET \
--endpoint "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/license/verify" \
--verify-server-sig \
--v 2 \
--program "My Product" \
--license abcd1234ef567890abcd1234ef567890 \
--email user@example.comDebug Mode with Request Body
Enable debug output to see detailed request/response information:
python CheckMyLicenseOnline.py \
--api-id a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d \
--api-secret YOUR_API_SECRET \
--endpoint "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/license/verify" \
--verify-server-sig \
--v 2 \
--program "My Product" \
--license abcd1234ef567890abcd1234ef567890 \
--email user@example.com \
--debug \
--show-bodySecurity Warning: The --show-body flag displays sensitive information including API secrets and signatures. Only use this during development and never paste the output into public logs or support tickets.
Debug Output Example
[WARNING] Showing full request body including sensitive fields.
{
"api_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"ts": "1765780335",
"payload": "eyJsaWNlbnNlIjoiYWJjZDEyMzRlZjU2Nzg5MGFiY2QxMjM0ZWY1Njc4OTAi...",
"sig": "Z352b7rGp3M7whdMBAC7/Kz9JzlfmndyKs+T69QC6GQ=",
"v": "2"
}
[DECODED PAYLOAD]:
{
"license": "abcd1234ef567890abcd1234ef567890",
"email": "user@example.com",
"target_id": "123456789",
"target_host": "MyPC\\MyApp",
"type": "Live",
"program": "My Product"
}
[DEBUG] Full server response JSON:
{
"success": {
"success": true,
"message": "activation exists",
"data": {
"validity": null,
"limit": 5,
"used": 2,
"offline_token": "eyJwcm9ncmFtX25hbWUi...",
"offline_expires_at": 1766385138
},
"server_sig": "sY+rvzHd7lTz8I+xP8IdqtN/gVEfbSx++ZbrHR7mj7A=",
"server_ts": "1765780338"
}
}
[DEBUG] Canonical response string used for signature:
a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d.1765780338.true.activation exists.eyJ2YWxpZGl0eSI...
[DEBUG] Expected server_sig (computed locally):
sY+rvzHd7lTz8I+xP8IdqtN/gVEfbSx++ZbrHR7mj7A=
[DEBUG] server_sig (found on response):
sY+rvzHd7lTz8I+xP8IdqtN/gVEfbSx++ZbrHR7mj7A=
Server signature verified OKIntegrating into Python Applications
You can import and use the license client as a Python module:
from CheckMyLicenseOnline import verify_license
# Configure your credentials
api_id = "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
api_secret = "YOUR_API_SECRET"
endpoint = "https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1/license/verify"
# Verify license
result = verify_license(
api_id=api_id,
api_secret=api_secret,
endpoint=endpoint,
license_key="abcd1234ef567890abcd1234ef567890",
email="user@example.com",
program="My Product",
verify_server_sig=True
)
if result.get("success"):
print("License verified successfully!")
data = result["success"]["data"]
print(f"Activation limit: {data['limit']}")
print(f"Currently used: {data['used']}")
else:
print(f"Verification failed: {result.get('error', {}).get('message')}")Response Data
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the verification succeeded |
message | string | "activation exists" or "activation created" |
validity | number/null | License expiry timestamp (null = no expiry) |
limit | number | Maximum activations allowed (0 = unlimited) |
used | number | Current number of activations |
offline_token | string | Base64 token for offline validation (optional) |
offline_expires_at | number | Unix timestamp when offline token expires |
server_sig | string | Server's HMAC-SHA256 signature of response |
server_ts | string | Server timestamp (Unix seconds) |
Best Practice: Always enable server signature verification (--verify-server-sig) in production to ensure response authenticity. Store API credentials securely using environment variables or secure configuration files.