# Licensing lifecycle (target contract)

> Documentation Preview — target API contract; endpoints may not be operational.

1. Seller completes its own PO/contract/invoice process outside LicenChain.
2. Seller backend calls `POST /api/integrations/v1/licensing-requests` with approved order data → receives `requestId` and `onboarding.url`.
3. Seller sends `onboarding.url` to the Buyer contact through its own channel.
4. Buyer signs in to LicenChain and accepts the product's predefined terms version.
5. LicenChain sends `onboarding.completed`. This is **not** issuance — do not grant access yet.
6. LicenChain issues **one** corporate license and sends `licence.issued` with the canonical `licenceKey`. Store it.
7. Seller backend calls `POST /api/integrations/v1/licenses/verify` before granting new access; only `valid === true` passes.

Licensing evidence (issuance) is distinct from the current active state (verification) and from
application authentication (the Seller's own identity provider).

## onboarding.completed

```json
{
  "schemaVersion": "1.0",
  "eventId": "evt_5N1R8T2W6Y0B",
  "type": "onboarding.completed",
  "occurredAt": "2026-11-12T14:05:00Z",
  "environment": "sandbox",
  "externalOrderId": "SELLER-PO-EXAMPLE-001",
  "requestId": "lreq_7Q2M9K4X8P3D",
  "data": {
    "onboardingStatus": "completed",
    "sellerId": "sel_3J6K9M2P5R8T",
    "productRef": "ai-specialist-standard",
    "buyerOrganizationId": "org_2B5D8F1H4K7M",
    "termsVersion": "2026.2",
    "termsAcceptedAt": "2026-11-12T14:04:51Z",
    "transactions": [
      {
        "transactionId": "txn_8F4D2K9Q1M7C"
      }
    ]
  }
}
```

## licence.issued

```json
{
  "schemaVersion": "1.0",
  "eventId": "evt_9C3V7X1Z5A2D",
  "type": "licence.issued",
  "occurredAt": "2026-11-12T14:20:00Z",
  "environment": "sandbox",
  "externalOrderId": "SELLER-PO-EXAMPLE-001",
  "requestId": "lreq_7Q2M9K4X8P3D",
  "data": {
    "licenceKey": "11155111:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:42",
    "statusAtIssuance": "not_yet_valid",
    "validFrom": "2027-01-01T00:00:00Z",
    "validUntil": "2028-01-01T00:00:00Z",
    "seatLimit": 25,
    "sellerId": "sel_3J6K9M2P5R8T",
    "productRef": "ai-specialist-standard",
    "buyerOrganizationId": "org_2B5D8F1H4K7M",
    "licenceHolderId": "org_2B5D8F1H4K7M",
    "termsVersion": "2026.2",
    "termsAcceptedAt": "2026-11-12T14:04:51Z",
    "onboardingEventId": "evt_5N1R8T2W6Y0B",
    "transactionId": "txn_8F4D2K9Q1M7C"
  }
}
```

`licence.issued` data: `licenceKey` (canonical, same key used for verification), `statusAtIssuance`
(active | not_yet_valid; snapshot only), `validFrom`/`validUntil` (UTC), `seatLimit` (allowance within the
one license, or null), `sellerId`, `productRef`, `buyerOrganizationId`, `licenceHolderId` (may differ from the
Buyer), `termsVersion` + `termsAcceptedAt` (accepted terms; equal to onboarding.completed),
`onboardingEventId` (links to onboarding.completed), `transactionId` (support reference). The envelope's
`requestId` and `externalOrderId` link it to the licensing request. Identifiers only, no personal data.

## Webhook authentication

Webhooks are **not** authenticated with the Seller API key (OpenAPI scheme `webhookSignature`, not
`sellerApiKey`). Headers: `LC-Timestamp` (Unix seconds), `LC-Event-Id` (= `eventId`),
`LC-Signature: v1=<lowercase hex HMAC-SHA256 of "<LC-Timestamp>.<raw request body>">` keyed with the
webhook signing secret. Verify over the raw body before parsing, compare in constant time, reject
deliveries more than 5 minutes from your clock, de-duplicate by `LC-Event-Id`.

## Missed licence.issued

Verification needs the `licenceKey`, which only `licence.issued` delivers, so verification cannot recover
a missed issuance. Do not grant access and never guess the key. Target support-assisted recovery (not yet operational): fix the endpoint and contact LicenChain support quoting
`requestId` and `externalOrderId` so issuance can be investigated. A secure manual redelivery
process has **not** yet been implemented or guaranteed; continue to deny access until the canonical
`licenceKey` has been recovered through an authenticated channel and verified. A status lookup by
`requestId`/`externalOrderId` is a separate future capability, not part of this contract.
