Skip to main content

Webhooks

Webhooks notify your server in real-time when events occur in your Licentric account. Register HTTPS endpoints to receive POST requests with event payloads.

Register Webhook

POST/webhooks

Register a new webhook endpoint. Requires API Key auth with webhooks:write scope.

Request Body

ParameterTypeRequiredDescription
urlstringRequiredHTTPS webhook endpoint URL (no private IPs or localhost)
eventsstring[]OptionalEvent types to subscribe to (min 1, default: ["*"] for all events)
Request
{
  "url": "https://example.com/webhooks/licentric",
  "events": ["license.created", "license.suspended", "machine.activated"]
}
201Webhook registered
json
{
  "data": {
    "id": "b3c4d5e6-8f17-9203-e467-df1a2b3c4d5e",
    "url": "https://example.com/webhooks/licentric",
    "events": ["license.created", "license.suspended", "machine.activated"],
    "accountId": "a0b1c2d3-7f15-8192-d356-ce0f1a2b3c4d",
    "createdAt": "2026-01-20T16:00:00.000Z",
    "updatedAt": "2026-01-20T16:00:00.000Z",
    "deletedAt": null
  }
}

List Webhooks

GET/webhooks

List registered webhook endpoints. Requires API Key auth with webhooks:read scope.

Query Parameters

ParameterTypeRequiredDescription
cursoruuidOptionalPagination cursor from previous response
limitintegerOptionalResults per page (1-100, default 25)
Request
GET /api/v1/webhooks?limit=25
200Paginated list
json
{
  "data": [
    {
      "id": "b3c4d5e6-8f17-9203-e467-df1a2b3c4d5e",
      "url": "https://example.com/webhooks/licentric",
      "events": ["license.created", "license.suspended", "machine.activated"],
      "createdAt": "2026-01-20T16:00:00.000Z",
      "updatedAt": "2026-01-20T16:00:00.000Z",
      "deletedAt": null
    }
  ],
  "pagination": { "nextCursor": null, "hasMore": false }
}

Event Types

Subscribe to specific events or use * to receive all events.

EventDescription
license.createdA new license was created
license.updatedA license was updated
license.suspendedA license was suspended
license.reinstatedA suspended license was reinstated
license.revokedA license was permanently revoked
license.renewedA license expiration was extended
license.expiredA license reached its expiration date
license.deletedA license was soft-deleted
machine.activatedA machine was activated
machine.deactivatedA machine was deactivated
machine.heartbeatA machine heartbeat was received
machine.deadA machine missed its heartbeat window
validation.successA license validation succeeded
validation.failedA license validation failed

Webhook Payload

Webhook payloads are sent as POST requests with a JSON body.

Webhook Payload
{
  "id": "evt_c4d5e6f7-9a28-0314-f578-e01a2b3c4d5e",
  "event": "license.created",
  "createdAt": "2026-03-01T14:00:00.000Z",
  "data": {
    "id": "c8f4e9a2-3b71-4d5e-9f12-8a6b3c7d4e5f",
    "key": "DSK-A1B2-C3D4-E5F6-G7H8",
    "status": "active",
    "productId": "d9e5f0a1-4c82-5e6f-a023-9b7c4d8e5f60"
  }
}

Signature Verification

Every webhook request includes an X-Licentric-Signature header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret.

Signature Header
X-Licentric-Signature: sha256=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2

Node.js Verification

verify-webhook.ts
import { createHmac, timingSafeEqual } from "crypto";

function verifyWebhookSignature(
  body: string,
  signature: string,
  secret: string
): boolean {
  const expected = createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  const sig = signature.replace("sha256=", "");
  return timingSafeEqual(
    Buffer.from(sig, "hex"),
    Buffer.from(expected, "hex")
  );
}

Python Verification

verify_webhook.py
import hmac
import hashlib

def verify_webhook_signature(
    body: bytes, signature: str, secret: str
) -> bool:
    expected = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    sig = signature.replace("sha256=", "")
    return hmac.compare_digest(sig, expected)
Always Verify Signatures
Always verify the HMAC-SHA256 signature before processing webhook payloads. Use constant-time comparison to prevent timing attacks.
Delivery Guarantees
Webhooks are delivered at least once. Your endpoint should be idempotent and use the event id to deduplicate. Failed deliveries are retried with exponential backoff.