Skip to content

Webhooks

LicenChain notifies your backend about lifecycle milestones by sending signed POST requests to an HTTPS endpoint you register during Seller onboarding.

Event Meaning Your action
onboarding.completed The Buyer accepted the applicable terms version and completed onboarding. Mark the order as accepted and record termsVersion. Do not grant licensed access yet.
licence.issued The one corporate license for the licensing request has been issued. Store data.licenceKey with the order, together with the validity period, seatLimit and accepted termsVersion.
{
"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"
}
]
}
}
{
"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"
}
}
Field Description
externalOrderId, requestId (envelope) Link the license to your order and to the licensing request that produced it.
data.licenceKey Canonical license identifier from the actual issuance. Store it exactly as received; it is the same key you send to Verify license.
data.statusAtIssuance Initial state: active, or not_yet_valid when issued before validFrom. A snapshot only — the current state comes from verification.
data.validFrom, data.validUntil License period in UTC (validFrom inclusive, validUntil exclusive).
data.seatLimit Seat or unit allowance recorded on this one license (the request quantity), or null when the product has none. Your delivery system enforces it.
data.sellerId Your LicenChain Seller account ID. Informational; your endpoint and signing secret already identify you.
data.productRef The registered product that was licensed.
data.buyerOrganizationId LicenChain ID of the Buyer organization.
data.licenceHolderId LicenChain ID of the organization assigned the rights in this license record. It may differ from the Buyer; never assume they are equal.
data.termsVersion, data.termsAcceptedAt The product terms version the Buyer accepted and when. Matches onboarding.completed.
data.onboardingEventId eventId of the onboarding.completed event that recorded the acceptance.
data.transactionId LicenChain support reference. Not a license identifier.

Payloads contain identifiers only — never Buyer names, email addresses, onboarding links, API keys or other secrets. Look up names in your own order records by externalOrderId.

Webhooks are sent by LicenChain to you, so they are not authenticated with your Seller API key or any Authorization header. Each delivery is authenticated by an HMAC signature computed with the webhook signing secret issued for your endpoint during Seller onboarding. In the OpenAPI target document this is the webhookSignature security scheme, set on both webhook operations.

Each delivery carries these headers:

Header Description
LC-Timestamp Unix time in seconds when the delivery was signed.
LC-Event-Id Same value as eventId in the body. Use it to de-duplicate.
LC-Signature v1= followed by the lowercase hex HMAC-SHA256 of <LC-Timestamp>.<raw request body>, keyed with your webhook signing secret.

To verify a delivery:

  1. Read the raw request body bytes before parsing JSON. Re-serialized JSON will not match.
  2. Compute HMAC-SHA256(signingSecret, "<LC-Timestamp>.<raw body>") as lowercase hex and prefix it with v1=.
  3. Compare it with LC-Signature in constant time.
  4. Reject the delivery with 401 if the signature does not match or LC-Timestamp is more than five minutes away from your clock.
import { createHmac, timingSafeEqual } from 'node:crypto';
const signingSecret = process.env.LICENCHAIN_WEBHOOK_SECRET ?? '';
const MAX_AGE_SECONDS = 300;
// rawBody must be the exact bytes received, before any JSON parsing.
export function isAuthenticLicenChainEvent(rawBody: string, headers: Headers): boolean {
const timestamp = headers.get('LC-Timestamp') ?? '';
const signature = headers.get('LC-Signature') ?? '';
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!/^\d{10}$/.test(timestamp) || age > MAX_AGE_SECONDS) return false;
const expected = 'v1=' + createHmac('sha256', signingSecret).update(`${timestamp}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && timingSafeEqual(a, b);
}
  • Respond with any 2xx status once you have durably recorded the event. Do the real work asynchronously.
  • Deliveries are at least once. De-duplicate by eventId and return 2xx for duplicates.
  • Events can arrive out of order. Use type, occurredAt and your stored state, not arrival order.
  • Non-2xx responses, timeouts and network errors are retried with exponential backoff for a bounded period. Signature or validation failures on your side should return 4xx and be investigated.

Verification cannot recover a missed issuance. Verify license requires the canonical licenceKey, and in this contract version licence.issued is the only way that key is delivered to you. If every retry of that event fails — for example because your endpoint was down for longer than the retry period — you do not have the key, and you must not grant licensed access for that order.

Detect the gap yourself: an order that received onboarding.completed but no licence.issued within your expected time is a reconciliation case. Never derive, guess or reconstruct a licenceKey, and do not substitute requestId, externalOrderId or transactionId for it.

Support-assisted recovery — target procedure, not yet operational

Section titled “Support-assisted recovery — target procedure, not yet operational”

The steps below describe the intended recovery process, not a currently implemented or guaranteed support service. Secure manual event redelivery and its operating procedures still need implementation and acceptance. Until a verified recovery is available, deny licensed access for orders without a canonical licenceKey:

  1. Fix and test your webhook endpoint and signature verification first.
  2. Contact LicenChain support through your onboarding channel and quote the requestId and externalOrderId (and transactionId, if you received onboarding.completed). Never send your API key, signing secret or onboarding link.
  3. Ask LicenChain support to investigate issuance status and identify an authenticated recovery method. Do not assume signed event redelivery can currently be performed.
  4. If LicenChain has implemented and validated secure redelivery, process the recovered licence.issued event like the original (verify the signature, de-duplicate by eventId, store the canonical licenceKey), then verify the license before granting access. Otherwise continue to deny access until recovery is available.

Self-service reconciliation (separate future capability)

Section titled “Self-service reconciliation (separate future capability)”

A Seller-scoped licensing-request status lookup by requestId or externalOrderId — returning the request status and, once issued, the canonical licenceKey — is the intended long-term reconciliation mechanism. It is a separate future capability: it is not part of this contract version, not in the OpenAPI target document, and must not be relied on. This delivery focuses on the two primary APIs: Submit a licensing request and Verify license.