CheckMyLicense LogoCheckMyLicense

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.

User Guide

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.

  1. Go to Programs and click Add Program.
  2. Enter a Program Name and Program Code (unique short identifier). Optionally upload a logo and write a description.
  3. 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.
  4. 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.

  1. Navigate to Licences and click Add Licence.
  2. Select the Program, type the Customer Email, set a Limit (devices allowed, 0 = unlimited) and an Expiry Date (or leave blank for unlimited).
  3. 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

  1. Navigate to Products and select Add Product.
  2. Specify a Title and Product Code, select the Program it belongs to and set the Currency and Price.
  3. Mark Discountable if referral codes should apply discounts.
  4. Define Limit (number of licences per purchase, 0 = unlimited) and Validity (days before licence expiry, 0 = no expiry).
  5. Write a description for storefront display.
  6. 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

  1. Navigate to Referees and click Add Referee.
  2. Provide the contact's email, first name and last name.
  3. Enter phone number, country, state and address.
  4. Click Save.
  5. 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.

  1. Go to Customers and click Add Customer.
  2. Enter the Email, First Name, Last Name, Phone, Country, State and Address. Click Create.
  3. 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

  1. Navigate to Orders and click Create Order. Select a Customer. Optionally choose a Referral Code; the discount value is calculated automatically.
  2. 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.
  3. Click Create Order. A confirmation modal shows the order code and total. Confirming sets the status to Draft. Open the order details to review.
  4. 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

  1. Go to Payments and click Add Payment.
  2. 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
  3. 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

  1. Organization Information: Edit fields such as organisation name, type (Personal or Company), phone, email, country, website, Tax ID and billing address.
  2. 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.
    Keep your API keys secure and do not share them publicly. Only active keys will authenticate successfully.
  3. 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

  1. 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.
  2. Check the box agreeing that credits will pay current and future dues.
  3. 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:

  1. Create a Program and add a version under Operations → Programs.
  2. Create a Product linked to that program under E‑Commerce → Products.
  3. (Optional) Add a Referee and Referral Code if you wish to offer discounts.
  4. Add a Customer under E‑Commerce → Customers.
  5. Create an Order for the customer in Orders. Select the product, apply any referral code and confirm the order. The order enters Draft status.
  6. Confirm the Order via the blue check icon on the order page. The status becomes Pending Payment.
  7. 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).
  8. 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.

API Documentation

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

POST/signin/email-password

Request body: email and password.

ParameterTypeInRequiredDescription
emailstringbodyyesUser email
passwordstringbodyyesUser password
Example Request
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!"
}
Example Response
{
  "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

POST/license/create

Create a new license. Implemented in functions/license/create. The endpoint requires organization context (auth provides organization_id).

ParameterTypeInRequiredDescription
emailstringbody or queryyesLicense owner email
program_namestringbody or queryyesProgram name
limitintegerbody or querynoActivation limit
expirybigintbody or querynoExpiry timestamp (seconds or ms accepted)
activations_deletablebooleanbody or querynoWhether activations can be deleted
cURL Example
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

200License created

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"
      }
    }
  }
}
400Invalid request / validation failed

Missing or invalid parameters

{
  "error": {
    "message": "Invalid license request",
    "errors": ["email is required", "program_name not found"]
  }
}
400Missing organization

Caller is not associated with an organization

{
  "error": {
    "message": "User must belong to an organization to create licenses"
  }
}
400Program validation failed

Program not owned by organization or not found

{
  "error": {
    "message": "Program not found or not owned by this organization"
  }
}
400Subscription limit / quota prevented creation

Subscription does not allow creating more licenses

{
  "error": {
    "message": "Subscription limits prevent creating new licenses",
    "reason": "license limit reached for current package"
  }
}
500Insert error

General failure while processing the request

{
  "error": {
    "message": "Error creating license",
    "data": { "message": "<error object>", "stack": "..." }
  }
}

License Update

Update License

PUT/license/update

Update license fields such as status. Implemented in functions/license/update. The handler expects license_id and any fields to set.

ParameterTypeInRequiredDescription
license_idintegerquery or bodyyesLicense ID to update
statusintegerquery or bodyno0=active, 1=inactive (will set deactivated_at)
cURL Example
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 }'
Example Response
{
  "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

POST/license/verify

Verify a license and create an activation record using HMAC-SHA256 signature authentication. Implemented in functions/license/verify.

ParameterTypeInRequiredDescription
api_idstring (UUID)bodyyesOrganization ID (from Settings → Organization)
tsstringbodyyesCurrent Unix timestamp in seconds (UTC)
payloadstring (base64)bodyyesBase64-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.
sigstring (base64)bodyyesHMAC-SHA256 signature of canonical string (base64 encoded)
vstringbodynoAPI version (default: "2")
cURL Example
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 verification payload
{
  "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 key
  • email: License owner's email address (must match the license, comparison is case-insensitive)
  • target_id: Account number or unique machine identifier
  • target_host: Server name or hostname
  • type: 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

StatusMessageDescription
200activation existsLicense verified successfully. This target already has an active license.
200activation createdFirst time this target is checking this license. New activation was created.
400missing required fieldsOne or more required fields (api_id, ts, payload, sig) are missing or empty.
401stale timestampTimestamp is too old or too far in the future (must be within ±60 seconds of server time).
401invalid api_idAPI Key (api_id) not found or inactive in the system.
401invalid signatureHMAC-SHA256 signature verification failed. Verify your api_secret and signature computation.
400invalid payloadPayload cannot be decoded from base64 or is not valid JSON.
404unmatched licenseNo license matching the provided UUID exists for this organization.
400license inactiveLicense status is 1 (inactive/deactivated). Cannot use inactive licenses.
400invalid emailProvided email does not match the license owner email.
400expired certificateLicense expiry timestamp has passed. License has expired.
400max limit reachedMaximum number of activations for this license has been exceeded.
500internal errorInternal 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_id and the license's last_used_at is updated.
  • Activation insertion is best-effort; verification succeeds even if activation DB insert fails.
  • If an activation with matching target_id, target_host, and type already 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:

  1. Prepare JSON payload with license, email, and target info
  2. Base64 encode the JSON payload
  3. Get current Unix timestamp (seconds, UTC)
  4. Build canonical string exactly as: api_id.ts.payload_base64 — where payload_base64 is the literal base64 string you include in the payload field. Do NOT decode or alter the base64 string when creating the canonical string.
  5. Compute HMAC-SHA256 of canonical string using your api_secret
  6. Base64 encode the HMAC result
  7. Send JSON request with api_id, ts, payload, sig, and v fields

Building the Payload

The JSON payload contains the license and activation information:

Example payload (before base64 encoding)
{
  "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 key
  • email: Email address of the license owner
  • target_id: Account login number or unique machine ID
  • target_host: Server hostname or terminal identifier
  • type: Account type (e.g., "Demo", "Live")
  • program: Program name (must match the license's program)
Example payload (base64 encoded)
eyJsaWNlbnNlIjoiOTg0NWNjYTk4YmJlNDY2NGJiYmM5ZmIxMDg2MmE0NTMiLCJlbWFpbCI6ImpvaG5AZXhhbXBsZS5jb20iLCJ0YXJnZXRfaWQiOiIxMjM0NTY3ODkiLCJ0YXJnZXRfaG9zdCI6Ik1UNS1EZXNrdG9wXFxFVVJVU0QiLCJ0eXBlIjoiTGl2ZSIsInByb2dyYW0iOiJNeSBQcm9kdWN0In0=
Canonical String Example
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:

  1. Take the canonical string: api_id.ts.payload_base64
  2. Compute HMAC-SHA256 using your api_secret as the key and the canonical string as the data
  3. Base64 encode the resulting HMAC digest
  4. Use this base64-encoded value as the sig parameter

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:

Canonical Response String Format
${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.

Example Canonical Response String
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

Full JSON request body
{
  "api_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "ts": "1729875123",
  "payload": "eyJsaWNlbnNlIjoiOTg0NWNjYTk4YmJlNDY2NGJiYmM5ZmIxMDg2MmE0NTMiLCJlbWFpbCI6ImpvaG5AZXhhbXBsZS5jb20iLCJ0YXJnZXRfaWQiOiIxMjM0NTY3ODkiLCJ0YXJnZXRfaG9zdCI6Ik1UNS1EZXNrdG9wXFxcXEVVUlVTRCIsInR5cGUiOiJMaXZlIiwicHJvZ3JhbSI6Ik15IFByb2R1Y3QifQ==",
  "sig": "BASE64_HMAC_SIGNATURE",
  "v": "2"
}

Breaking it down:

  • api_id = Organization ID
  • ts = 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, and data with validity/limit/used.
  • 400-500 — Error. Response contains error object with code and message.
Success response (200 OK)
{
  "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 activations
  • offline_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 response (e.g., 401 Unauthorized)
{
  "error": {
    "code": 401,
    "message": "invalid signature"
  }
}

Signature Verification Failed?

  • Verify your api_secret is 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 sig field

Create Customer

Create Customer

POST/order/customer/create

Creates 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.

ParameterTypeInRequiredDescription
customer.emailstringbodyyesCustomer email address. Must be valid format and unique within organization.
customer.first_namestringbodyyesCustomer first name. Cannot be empty.
customer.last_namestringbodyyesCustomer last name. Cannot be empty.
customer.countrystringbodynoCountry name or code.
customer.statestringbodynoState, province, or region.
customer.addressstringbodynoFull street address.
customer.phonestringbodynoPhone number (international format supported). Must have at least 7 digits.
customer.refereebooleanbodynoWhether customer is a referee. Defaults to false.
Example Request
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

200Customer created successfully

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"
    }
  }
}
400Bad Request

Missing required fields, invalid email format, invalid phone format, or duplicate email

{
  "error": {
    "code": 400,
    "message": "Invalid email format"
  }
}
401Unauthorized

Missing or invalid JWT token

{
  "error": {
    "code": 401,
    "message": "Authentication required"
  }
}
403Forbidden

User lacks organization membership

{
  "error": {
    "code": 403,
    "message": "Organization membership required"
  }
}
409Conflict

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_id and organization_id are automatically set from JWT context

Submit Order

Submit Order

POST/order/submit

Creates 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.

ParameterTypeInRequiredDescription
order.codestringbodynoCustom order code. Auto-generated if omitted.
order.customer_idintegerbodyyesID of the customer placing the order. Must belong to organization.
order.referral_codestringbodynoReferral/discount code to apply.
order.itemsarraybodyyesArray of line items with product_id and qty.
order.items[].product_idintegerbodyyesProduct ID. Must belong to organization.
order.items[].qtyintegerbodyyesQuantity (positive integer). Duplicates are consolidated.
Example Request
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

200Order created successfully

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": [...]
    }
  }
}
400Bad Request

Invalid customer_id, missing items, invalid product prices, or duplicate order code

{
  "error": {
    "code": 400,
    "message": "Customer not found in organization"
  }
}
404Not Found

Customer or products not found in organization

{
  "error": {
    "code": 404,
    "message": "Product not found"
  }
}

Update Order

Update Order

POST/order/update

Updates 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.

ParameterTypeInRequiredDescription
order.idintegerbodyyesID of the order to update. Must be in draft status (0).
order.codestringbodynoNew order code (optional). Must be unique within organization.
order.referral_codestringbodynoNew referral code. null removes existing referral.
order.itemsarraybodyyesComplete replacement item list. Previous items are deleted.
order.items[].product_idintegerbodyyesProduct ID. Must belong to organization.
order.items[].qtyintegerbodyyesQuantity (positive integer).
Example Request
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

200Order updated successfully

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": [...]
    }
  }
}
400Bad Request

Invalid order id, non-draft status, missing items, duplicate code, or invalid products

{
  "error": {
    "code": 400,
    "message": "Order is not in draft status"
  }
}
403Forbidden

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_at timestamp is automatically set

Confirm Order

Confirm Order

POST/order/confirm

Transitions 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.

ParameterTypeInRequiredDescription
order_idintegerbodyyesID of the order to confirm. Must be in draft status (0).
Example Request (Primary format)
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
  }'
Alternative Request Format
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

StatusLabelDescription
0DraftOrder created but not confirmed
1Pending PaymentOrder confirmed, awaiting payment
2Partially PaidSome payment received
3PaidFully paid
4CancelledOrder cancelled
5RefundedPayment refunded
6Partially RefundedPartial refund issued

Possible Responses

200Order confirmed

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"
    }
  }
}
400Bad Request

Invalid order_id or order not in draft status

{
  "error": {
    "code": 400,
    "message": "Order is not in draft status"
  }
}
404Not Found

Order not found in organization

{
  "error": {
    "code": 404,
    "message": "Order not found"
  }
}

Cancel Order

Cancel Order

POST/order/cancel

Cancels 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.

ParameterTypeInRequiredDescription
order_idintegerbodyyesID of the order to cancel.
refundobjectbodynoRefund configuration. Omit for no refund.
refund.enabledbooleanbodynoSet to true to create refund payment.
refund.amountnumberbodynoSpecific refund amount. Omit for full auto-calculated refund.
deactivate_licensesbooleanbodynoSet to true to queue license deactivation job.
notesstringbodynoCancellation notes (stored in order.notes).
Example Request (Simple 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,
    "notes": "Customer requested cancellation"
  }'
Example Request (With Partial Refund and License Deactivation)
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

Scenariorefund.enableddeactivate_licensesBehavior
Simple Cancelfalse or omittedfalse or omittedSets status to 4, updates canceled_at
Cancel with Full Refundtrue, no amountfalse or omittedCancels + creates refund for all captured funds
Cancel with Partial Refundtrue, with amountfalse or omittedCancels + creates refund for specified amount
Cancel with License Revocationfalse or omittedtrueCancels + queues license deactivation job
Full Cancel PackagetruetrueCancels + refunds + queues license deactivation

Possible Responses

200Order cancelled successfully

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
    }
  }
}
200Order cancelled with refund and license deactivation

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
    }
  }
}
400Bad Request

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_amount and available captured funds
  • License deactivation is asynchronous (job-based) and non-blocking
  • Multiple cancellations are idempotent when no actions are requested
  • canceled_at timestamp is set on first cancellation only

Record Payment

Record Payment

POST/order/payment

Records 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).

ParameterTypeInRequiredDescription
payment.order_codestringbodyyesOrder code to apply payment to.
payment.providerstringbodyyesPayment provider name (e.g., "stripe", "paypal", "manual").
payment.provider_txn_idstringbodyyesUnique transaction ID from payment provider.
payment.amountnumber/stringbodyyesPayment amount (positive number). Accepts numeric strings.
payment.statusintegerbodyyesPayment status: 1 = captured, 2 = failed, 3 = refund.
payment.payment_methodstringbodynoPayment method type (e.g., "card", "bank_transfer").
payment.external_referencestringbodynoAdditional reference ID (e.g., customer ID, invoice number).
Example Request (Captured Payment)
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"
    }
  }'
Example Request (Refund)
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

StatusLabelDescriptionEffect on Order
1CapturedPayment successfully receivedAdds to captured total; updates order status when fully paid
2FailedPayment attempt failedRecorded for audit; no effect on order status
3RefundMoney returned to customerRecorded; does NOT reduce captured total per spec

Order Status Updates

ScenarioOrder StatusDescription
First captured payment < final_amount2 (Partially Paid)Some payment received
Captured total >= final_amount3 (Paid)Fully paid; paid_at timestamp set
Failed paymentNo changeStatus remains as-is
Refund paymentNo changeUse /order/cancel with refund for status updates

Possible Responses

200Payment recorded successfully

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"
    }
  }
}
200Partial payment recorded

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
    }
  }
}
400Bad Request

Missing required fields, invalid status, invalid amount, or duplicate provider_txn_id

{
  "error": {
    "code": 400,
    "message": "Invalid payment status"
  }
}
404Not Found

Order code not found in organization

{
  "error": {
    "code": 404,
    "message": "Order not found"
  }
}
409Conflict

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_id must 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/cancel endpoint for full refund workflow
SDK Documentation

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

  1. Download the SDK
  2. Extract Files

    The SDK package contains two files:

    • CheckMyLicense.mqh – The reusable SDK library
    • SHA26.mqh – The reusable component for SHA-256 hashing
    • CheckMyLicense.mq5 – Example implementation script
  3. Copy to MetaTrader Directory

    Copy CheckMyLicense.mqh and SHA26.mqh to your MetaTrader 5 Include folder:

    C:\Users\YourUsername\AppData\Roaming\MetaQuotes\Terminal\[TerminalID]\MQL5\Include\
  4. 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&apos;s email
input string InpLicence = ""; // License key

2. 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 CodeDescriptionSolution
EX1001Email not providedEnter your email address in the input parameters (InpEmail)
EX1002License key not providedEnter your license key in the input parameters (InpLicence)
EX1003Program name not configuredSet #define ProgramName to match your program name in CheckMyLicense
EX1004License validation failed at startupVerify your license key format and API credentials
EX2001Payload encoding failedVerify your #define APISecret is correctly set from Organization settings
EX2002HMAC-SHA256 signature computation failedVerify #define APIID and #define APISecret are correct
EX3001WebRequest URL not allowed in MT5Add https://vwqretlvkrravzguxydw.functions.eu-west-2.nhost.run/v1 to Tools → Options → Expert Advisors → Allow WebRequest for URLs
EX3003Connection error or timeoutCheck your internet connection; if persists, verify the server URL is accessible
EX4001-EX4005Cryptographic operation failed (Base64 encoding/decoding)Verify the license key is valid; check that SHA256.mqh is included in your project
400 Bad RequestInvalid payload or malformed requestVerify email format is correct and license key contains valid 32 hex characters
401 UnauthorizedLicense invalid, expired, or not owned by emailVerify the email matches the license owner; check license hasn't expired in your dashboard
404 Not FoundLicense or program not foundVerify the license key exists and the program name matches your CheckMyLicense dashboard
500 Server ErrorCheckMyLicense server errorTry again later; if error persists, contact support

Testing Your Implementation

  1. Compile – Compile your MQL5 file in MetaEditor (F7)
  2. Attach to Chart – Drag your EA/Indicator to a chart
  3. Enter Credentials – Input your email and license key in the parameters dialog
  4. Check Logs – Open the Experts tab to see verification results
  5. 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

  1. Download the SDK

    Download the Python SDK from your organization page (Available SDK section).

  2. Extract the File

    The SDK package contains a single file:

    • CheckMyLicenseOnline.py – The CLI tool and library
  3. Install Dependencies

    The SDK uses Python standard library only, no external dependencies required.

  4. 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.com

Command-Line Parameters

ParameterRequiredDescription
--api-idYesOrganization API ID from Settings → Organization
--api-secretYesOrganization API Secret from Settings → Organization
--endpointYesCheckMyLicense API endpoint URL
--licenseYes32-character hex license key
--emailYesLicense owner's email address
--programYesProgram name (must match license's program)
--vNoAPI version (default: 2)
--verify-server-sigNoEnable server signature verification (recommended)
--debugNoEnable detailed debug output
--show-bodyNoDisplay 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 OK

Examples

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.com

Debug 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-body

Security 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 OK

Integrating 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

FieldTypeDescription
successbooleanWhether the verification succeeded
messagestring"activation exists" or "activation created"
validitynumber/nullLicense expiry timestamp (null = no expiry)
limitnumberMaximum activations allowed (0 = unlimited)
usednumberCurrent number of activations
offline_tokenstringBase64 token for offline validation (optional)
offline_expires_atnumberUnix timestamp when offline token expires
server_sigstringServer's HMAC-SHA256 signature of response
server_tsstringServer 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.

License Management Documentation | CheckMyLicense API, MQL4/MQL5 SDK & MetaTrader Guides