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.
| Event | Description |
|---|---|
| license.created | A new license key was issued |
| license.activated | License was validated for the first time |
| license.updated | License metadata or settings were modified |
| license.suspended | License was suspended by vendor |
| license.reinstated | Suspended license was reactivated |
| license.revoked | License was permanently revoked |
| license.renewed | Expired license was renewed |
| license.expired | License reached its expiration date |
| license.deleted | License was soft-deleted |
| license.limit_reached | Machine activation hit the license limit |
| machine.activated | A new device was activated on a license |
| machine.deactivated | A device was removed from a license |
| machine.heartbeat | A device sent a heartbeat ping |
| machine.dead | A device missed its heartbeat deadline |
| validation.success | A license validation succeeded |
| validation.failed | A 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}, 200Never 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.
| Attempt | Delay | Notes |
|---|---|---|
| 1st | Immediate | First delivery attempt |
| 2nd | 1 minute | First retry after failure |
| 3rd | 5 minutes | Final 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.