Skip to content
Back to Launch documentation

Download MarkdownOriginal source on GitHub

Custom Authorization System Integration Guide

The current source code supports two authorization providers: Licentivo and custom_http. Merchants can select and enable one under System Settings → Authorization System, and all products and sandbox/live environments will use the same provider; custom connections and plan mappings are still configured separately by product and environment. This feature does not include a self-built authorization system; merchants continue to use their own authorization services and SDK.

Existing Licentivo account exchange, nine-language SDK, and device management continue to use the original process. Custom products synchronize purchase status according to this protocol, and clients read authorization delivery in the account center without going through the Licentivo SDK process.

Quick start

  1. Implement the following four HTTP interfaces in your own service, configuring independent HMAC signature keys and identifiers for each connection.
  2. In the 'Authorization System', select 'Custom Authorization Service', add a new connection, fill in the address, environment, connection identifier, key ID, and standard Base64 key, verify and save, then click 'Enable Custom Authorization Service'.
  3. Create product and sales plan drafts. Purchase type and price are managed in Launch; authorization rules are managed in your own system.
  4. Select the product and environment, select the connection, fill in external product and plan references, and save. The provider is determined by the site-wide selection and cannot enable Licentivo simultaneously for other products.
  5. Publish a test version on the sales plan page, complete acceptance tests for payment, delivery, renewal, upgrade, refund, and error recovery, then configure the production environment separately.

Once the address, environment, connection identifier, and network access policy are created, they cannot be changed; create a new connection if needed. Signature keys can be rotated; leave blank to retain the original key. When there are orders under another authorization system, the site-wide provider cannot be switched directly; migration must be completed first. After switching, the original plan becomes preview-only and needs to be republished; saved connection credentials and mappings are retained. Products with existing orders cannot switch connections directly, and mappings in sales cannot be changed directly; new prices or mappings use a new version of the plan.

When configuring via API, first call PUT /api/v1/admin/licensing/provider, with the request body being {"provider":"custom_http"} or {"provider":"licentivo"}, and then save the product mapping. Providers not enabled cannot save product authorization bindings or Licentivo plan strategy mappings.

Connection checks confirm protocols, authentication, environment, and supported purchase types, and will not create or confirm external packages on behalf of merchants. Sandbox integration acceptance must be completed using actual external packages before official payment collection.

3. Merchant Integration Process

  1. Implement the four interfaces in this document in front of the existing licensing system, preserving its existing license logic.
  2. Create a "Custom Authorization Service" connection in Launch, configuring the HTTPS address, connection identifier, environment, and signature key.
  3. Launch calls the capability check interface to confirm the protocol version, support for permanent/temporary purchases, and delivery method.
  4. Select this connection for the product; map the sales package version to external product_ref and plan_ref. External codes can use the merchant's own strings and are not required to use Licentivo UUID.
  5. Complete payment, renewal, upgrade, refund, and fault recovery acceptance in the sandbox, then separately publish the live configuration.
  6. After verifying payment, Launch saves the order, purchase coverage, and pending tasks within the same database transaction.
  7. The backend sends a complete purchase snapshot to the Custom Authorization Service; the service persists the received record before confirming receipt.
  8. The authorization service processes the target status. Launch queries the original event receipt, and only applied confirms that this synchronization is complete.
  9. Launch retrieves delivery information for logged-in customers who own the purchase and displays the authorization code, activation entry, or plain text instructions in the customer center. The application connects to the merchant's own authorization system.
  10. Renewal, upgrade, refund, withdrawal, suspension, and expiration all generate a new version of the complete snapshot. The authorization service processes according to the latest target status.

The sandbox/live connections of the same product are bound separately. After an existing purchase, changing the provider requires an independent migration process; old orders and old tasks cannot follow the backend selection and be silently sent to the new service.

4. APIs Required by Custom Authorization Service

The following interfaces are received by the merchant authorization service and called by the Launch server. base_url can include a fixed path prefix; the paths in the table are appended after this prefix.

Methods and Paths Purpose
GET /launch/v1/capabilities Connection Checks, Protocols, and Capability Declarations
POST /launch/v1/events Receive complete target purchase status; retry using the original event
GET /launch/v1/events/{event_id} Query whether the event is actually applied and restore lost responses
GET /launch/v1/entitlements/{entitlement_ref}/delivery Read already authorized delivery information without creating a second authorization

The first edition uses active query receipts and does not add external callbacks to modify orders. Custom services do not have the authority to confirm payment, change prices, or rewrite purchase periods.

4.1 General Conventions and Authentication

  • JSON uses UTF-8; time uses UTC RFC 3339; version uses positive integers.
  • A successful response is {"data": {...}, "request_id": "..."}; an error response is {"error": {"code": "...", "message": "..."}, "request_id": "..."}.
  • Each environment and connection uses a separate key. The key must be at least 32 random bytes, referenced in Launch from existing encrypted key storage, and not placed in frontend configuration, examples, or application packages.
  • Each request carries X-Launch-Key-Id, X-Launch-Timestamp, X-Launch-Nonce, X-Launch-Signature.
  • The timestamp is in decimal Unix seconds. Nonce is a base64url encoding of at least 128 bits of random value without padding, and a new value is generated for each network attempt.

Signature input consists of the following seven items, encoded as UTF-8 bytes and joined with a single newline, with no trailing newline:

v1
<key_id>
<timestamp>
<nonce>
<UPPERCASE HTTP method>
<encoded request path and raw query, including the base_url path prefix>
<lowercase hexadecimal SHA-256 digest of the exact request body>

X-Launch-Signature consists of the prefix v1= followed by the unpadded base64url encoding of the HMAC-SHA256 result. GET request bodies are empty. The recipient verifies the signature and time window before deserializing, uses constant-time comparison, allows a default clock deviation of ±300 seconds, and retains nonces for at least 600 seconds to prevent replay. Never reuse a nonce across retries; keep the event ID and business request body unchanged.

Signatures do not replace HTTPS. Requests do not carry customer passwords, payment keys, or Licentivo management keys; by default, only anonymous account references are sent, not email addresses. Server HTTP clients need timeouts, response size limits, and disabled credential forwarding on redirects. Custom addresses must be validated; intranet access must be configured explicitly by administrators.

4.2 Capability Check

GET /launch/v1/capabilities returns HTTP 200, for example:

{
  "data": {
    "protocol": "launch-external-licensing-v1",
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "purchase_types": ["perpetual", "fixed_term"],
    "delivery_types": ["license_key", "activation_url", "instructions"],
    "devices": false,
    "identity_exchange": false
  },
  "request_id": "check-001"
}

protocol, connection_ref, environment, purchase_types, delivery_types are required. The last two must be non-empty arrays, and unsupported purchase types cannot be listed for sale. The first versions devices and identity_exchange must be false; support can only be declared after the extended contract and implementation are completed.

4.3 Purchase Snapshot

Example request for POST /launch/v1/events:

{
  "schema": "launch-external-licensing-v1",
  "event_id": "7bf84b91-2749-4b01-a6a7-bd527c488ee8",
  "connection_ref": "merchant-01-sandbox",
  "environment": "sandbox",
  "type": "entitlement.snapshot",
  "change_reason": "purchase",
  "account_ref": "customer_opaque_001",
  "entitlement_ref": "purchase_opaque_001",
  "launch_product_id": "c6dd463e-50f7-4b76-8239-0a8ec6fc304b",
  "launch_plan_version_id": "5459a073-9362-4b46-949d-9b0c5d84421a",
  "product_ref": "desktop-editor",
  "plan_ref": "professional-yearly-v1",
  "version": 1,
  "desired_state": "active",
  "valid_from": "2026-10-25T00:00:00Z",
  "valid_until": "2027-10-25T00:00:00Z"
}

All fields in the example above are required. valid_until as null means permanent purchase; other values must be later than valid_from.

Field Constraints and meaning
event_id UUID generated by Launch; remains the same if the same event is retried
connection_ref / environment Must match the connection and environment of the signing key
account_ref Stable anonymous account reference; the receiver identifies the customer using the connection, environment, and this reference together
entitlement_ref Stable reference for one purchase relationship; if fully revoked, a new reference is used for repurchase
launch_product_id / launch_plan_version_id UUIDs of the Launch product and immutable plan version
product_ref / plan_ref External reference configured by the integrator; 1–128 ASCII characters, letters, numbers, _, -, . allowed; external packages are not automatically created
version Target version that increases monotonically under the same connection, environment, and purchase reference
desired_state active、suspended、expired、revoked
change_reason purchase、renewal、upgrade、refund、reversal、suspension、resumption、expiry、reconciliation

account_ref, entitlement_ref use 1–128 letters, numbers, underscores, or hyphens. schema, type are fixed to the example values. The receiver applies results according to desired_state and the full absolute term, without adding days or revoking the entire authorization on its own.

Especially for refunds: after returning a renewal or upgrade order, the valid base purchase may still remain; in this case, the snapshot can still be active, but the term is shortened or the package changes. Only after the ledger is recalculated and no valid purchase covers it, is the corresponding termination status sent.

4.4 Receiving, Acknowledgment, and Recovery

The receiver must return HTTP 202 only after persisting the event ID, the digest of the original request body, and the task to be executed; repeat requests that have already been synchronized can return HTTP 200. Both will return the following receipt structure.

{
  "data": {
    "event_id": "7bf84b91-2749-4b01-a6a7-bd527c488ee8",
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "entitlement_ref": "purchase_opaque_001",
    "version": 1,
    "payload_sha256": "<64-character lowercase hexadecimal SHA-256 digest of the exact event body>",
    "status": "pending",
    "error_code": null
  },
  "request_id": "receipt-001"
}

Replace the digest placeholder with the actual digest. Every receipt field above is required; error_code is null when there is no error. GET /launch/v1/events/{event_id} returns the same structure with HTTP 200. The status can be:

status Meaning Processing of Launch
pending Persisted but actual authorization change not yet completed Query delayed, cannot mark as synchronized
applied Authorization service confirms the target state has been actually applied Confirmed synchronization after checking all related fields and digest
rejected Clearly cannot be applied, e.g., external package does not exist Stop automatic retries, show a clear error
manual_review Result uncertain or state conflict, requires manual verification Original task and event are retained, waiting for auditable processing

Delivery status is independent of the sync receipt; applied cannot directly serve as operational permission for the client application.

Reliability requirements:

  • At least once delivery; the receiver must guarantee business idempotency; cannot promise “only call once” at the network layer.
  • Return existing results for the same event ID and same original request body; return HTTP 409 IDEMPOTENCY_CONFLICT for the same ID but different request body.
  • Older versions must not overwrite newer versions. Different events or targets of the same purchase version should not be silently accepted and should return HTTP 409 STATE_VERSION_CONFLICT for verification.
  • If a POST response is lost or the process restarts, Launch first does a GET on the original event; only after explicitly returning EVENT_NOT_FOUND should the original ID and request body be resent.
  • If the remote actual delivery result is unclear, the integrator should also use its own persisted idempotent reference for verification and cannot generate another authorization by just resending once.
  • HTTP 429 follows Retry-After; timeout and 5xx errors recover with backoff; exceeding the limit requires manual verification. Authentication, configuration, and business rejections should not be treated as successful synchronization.

Suggested error codes: SIGNATURE_INVALID (401), SIGNATURE_EXPIRED (401), NONCE_REPLAYED (409), SCOPE_MISMATCH (403), EVENT_NOT_FOUND (404), IDEMPOTENCY_CONFLICT (409), STATE_VERSION_CONFLICT (409), PLAN_UNKNOWN (422), PURCHASE_TYPE_UNSUPPORTED (422), RATE_LIMITED (429), SERVICE_UNAVAILABLE (503). Error messages must not include credentials.

4.5 Authorized Delivery

GET /launch/v1/entitlements/{entitlement_ref}/delivery can only read existing delivery results in this connection and environment and should not treat queries as new issuance commands. Example HTTP 200:

{
  "data": {
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "entitlement_ref": "purchase_opaque_001",
    "version": 1,
    "status": "ready",
    "delivery": {
      "type": "license_key",
      "value": "EXAMPLE-ONLY-NOT-A-REAL-LICENSE",
      "expires_at": null
    }
  },
  "request_id": "delivery-001"
}

status is pending, ready, or unavailable; when not ready, delivery is null. When ready, type must be a declared license_key, activation_url, or instructions, with value up to 4096 UTF-8 bytes, and expires_at as UTC time or null. All related fields in the response must match the current purchased version in Launch; older version deliveries cannot be treated as current results.

Authorization codes are sensitive data; delivery responses use Cache-Control: no-store, do not log the body, and should be stored encrypted if persistence is needed. Activation URLs only accept HTTPS sources allowed by the connection configuration; instructions are displayed as plain text, without executing HTML or scripts. Accounts that are revoked, fully refunded, or have no valid purchase cannot continue to get delivery info via Launch; actual expiration of delivered credentials is handled by external systems.

Launch Management and Customer API

These interfaces are under the Launch origin deployed by the merchant and do not use external service HMAC key authentication. Admins use existing website sessions, and write requests must include X-CSRF-Token. Customers can only read their own purchases.

Methods and Paths Purpose
GET /api/v1/admin/licensing/connections Returns connections, bindings, mappings, and default_provider; The connection list only contains custom connections, Licentivo the original management interface is retained
POST /api/v1/admin/licensing/connections Verify and add custom connections
PUT /api/v1/admin/licensing/connections/{id} Edit name, allowed activation sources, and signature configuration; Historical reception address remains unchanged
POST /api/v1/admin/licensing/connections/{id}/check Recheck; last_check_error in HTTP 200 that is not null means the check failed
PUT /api/v1/admin/products/{id}/licensing Provides provider of environment configuration for the product, mapping connections to external packages
GET /api/v1/me/products/{id}/delivery?environment=sandbox The current customer reads the currently valid, custom purchase authorization delivery; Cookie or desktop bearer authentication
GET /api/v1/licensing/documentation This document does not require login
GET /api/v1/licensing/protocol The OpenAPI required for custom services does not require login

Add a custom connection request body:

{
  "provider": "custom_http",
  "name": "My licensing service",
  "base_url": "https://licenses.example.com",
  "environment": "sandbox",
  "connection_ref": "merchant-01-sandbox",
  "key_id": "key-1",
  "signing_key": "<standard Base64 encoding of 32 to 128 random bytes>",
  "allow_private_network": false,
  "allow_local_http": false,
  "activation_origins": ["https://activate.example.com"]
}

Response data is the desensitized connection object and does not return the signature key or secret ID. allow_local_http Only allows the sandbox's localhost/loopback address; Private HTTPS requires explicit administrator permission. Leaving the key blank is only allowed to edit existing connections. Licentivo Still set via POST /api/v1/admin/connections/licentivo.

Configure the product request body:

{
  "provider": "custom_http",
  "environment": "sandbox",
  "connection_id": "<id returned when creating the connection>",
  "mappings": [
    {
      "version_id": "<Launch plan version UUID>",
      "product_ref": "desktop-editor",
      "plan_ref": "professional-yearly-v1"
    }
  ]
}

When Licentivo is selected, the {"provider":"licentivo","environment":"sandbox","mappings":[]} is sent, and its product/policy mapping still uses the original interface. The first switch turns an existing release into a preview, requiring rechecking and publishing by the new provider. Mappings can only include the version to be added or retained; Omitting will not delete old mappings.

Customer delivery is standard data envelope for HTTP 200; No purchase returns 404, Licentivo goods return 409 LICENSING_DELIVERY_UNSUPPORTED, unsynchronized, suspended, expired, or full refund returns 409 ENTITLEMENT_NOT_READY, external unavailable or receipt mismatch returns 503. Customer Center's "View Authorized Delivery" revalidates ownership and status, without writing credentials to URLs or logs.

The custom product's Licentivo identity, device, and integration-check interfaces return 409 CUSTOM_LICENSING_SDK_REQUIRED. The old /credential continues to return 410. Apps should use the merchant’s own SDK, rather than treating the Launch’s ready status as runtime authorization.

Examples and Acceptance

Python lightweight adapter example provides a receiving framework with signature, nonce, event deduplication, and receipt persistence; actual authorization handling requires integration with the merchant system.

The custom protocol’s OpenAPI and Launch own OpenAPI describe the two directions separately. The receiver does not need to implement Licentivo’s product catalog, policy catalog, or identity trust interfaces.

Local verification uses isolated MySQL, real HTTP adapter, and simulated authorization/payment services, covering response loss, restart, renewal, upgrade, refund, account deactivation, receipt mismatch, and delivery isolation. For production acceptance, the merchant’s own authorization service and app are also required. Unregistered devices, shared account login, and offline capabilities are not supplemented by Launch.