Skip to main content

Guides

Webhooks

Receive real-time notifications when licenses, machines, and validations change.

1. Register a Webhook Endpoint

Register an HTTPS URL to receive webhook events. Select which events you want to subscribe to. The response includes a signing secret for verifying payloads.

register.py
from licentric import Licentric

client = Licentric(api_key="lk_live_your_key_here")

webhook = client.webhooks.create(
    url="https://yourapp.com/webhooks/licentric",
    events=[
        "license.created",
        "license.revoked",
        "license.expired",
        "machine.activated",
        "validation.failed"
    ]
)

# Save the signing secret — you'll need it for verification
print(f"Signing secret: {webhook.secret}")
HTTPS required
Webhook URLs must use HTTPS and resolve to a public IP address. Private IPs and localhost URLs are rejected.

2. Available Events

Subscribe to any combination of these 14 event types.

EventDescription
license.createdA new license key was issued
license.activatedLicense was validated for the first time
license.updatedLicense metadata or settings were modified
license.suspendedLicense was suspended by vendor
license.reinstatedSuspended license was reactivated
license.revokedLicense was permanently revoked
license.renewedExpired license was renewed
license.expiredLicense reached its expiration date
license.deletedLicense was soft-deleted
license.limit_reachedMachine activation hit the license limit
machine.activatedA new device was activated on a license
machine.deactivatedA device was removed from a license
machine.heartbeatA device sent a heartbeat ping
machine.deadA device missed its heartbeat deadline
validation.successA license validation succeeded
validation.failedA license validation failed

3. Webhook Payload

Each webhook delivers a JSON payload with the event type, timestamp, and the full resource data.

Webhook payload example
{
  "id": "evt_a1b2c3d4e5f6...",
  "type": "license.created",
  "createdAt": "2026-01-15T10:30:00.000Z",
  "data": {
    "licenseId": "lic_xyz789",
    "productId": "prod_def456",
    "license": {
      "id": "lic_xyz789",
      "accountId": "acc_abc123",
      "productId": "prod_def456",
      "policyId": "pol_abc123",
      "status": "active",
      "name": null,
      "expiresAt": "2027-01-15T10:30:00.000Z",
      "userEmail": "customer@example.com",
      "metadata": {},
      "createdAt": "2026-01-15T10:30:00.000Z",
      "updatedAt": "2026-01-15T10:30:00.000Z"
    }
  }
}

4. Verify the Signature

Every webhook includes an X-Licentric-Signature header containing an HMAC-SHA256 signature of the raw request body. Always verify this signature before processing the event.

Node.js / TypeScript

verify.ts
import crypto from "crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  timestamp: string,
  secret: string
): boolean {
  // Reject timestamps older than 5 minutes (replay protection)
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
  if (age > 300) return false;

  const material = timestamp + "." + payload;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(material)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post("/webhooks/licentric", (req, res) => {
  const signature = req.headers["x-licentric-signature"];
  const timestamp = req.headers["x-licentric-timestamp"];
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, timestamp, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Process the event
  const event = req.body;
  handleEvent(event);
  res.status(200).json({ received: true });
});

Python

verify.py
import hmac
import hashlib
import time

def verify_webhook_signature(
    payload: bytes,
    signature: str,
    timestamp: str,
    secret: str
) -> bool:
    # Reject timestamps older than 5 minutes (replay protection)
    age = int(time.time()) - int(timestamp)
    if age > 300:
        return False

    material = f"{timestamp}.".encode() + payload
    expected = hmac.new(
        secret.encode(),
        material,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

# In your webhook handler (Flask example):
@app.post("/webhooks/licentric")
def handle_webhook():
    signature = request.headers.get("X-Licentric-Signature")
    timestamp = request.headers.get("X-Licentric-Timestamp")
    payload = request.get_data()

    if not verify_webhook_signature(payload, signature, timestamp, WEBHOOK_SECRET):
        return {"error": "Invalid signature"}, 401

    event = request.get_json()
    process_event(event)
    return {"received": True}, 200
Never skip verification
Always verify the HMAC signature before trusting webhook data. Without verification, an attacker could send forged events to your endpoint.

5. Retry Policy

Failed deliveries (non-2xx response or timeout) are retried with exponential backoff.

AttemptDelayNotes
1stImmediateFirst delivery attempt
2nd1 minuteFirst retry after failure
3rd5 minutesFinal retry before marking as failed

6. Delivery Statuses

  • pending — Delivery queued or in progress
  • success — Endpoint returned 2xx
  • failed — All retry attempts exhausted
Idempotency
Webhook events include a unique id field. Store processed event IDs and skip duplicates to ensure your handler is idempotent.