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
/webhooksRegister a new webhook endpoint. Requires API Key auth with webhooks:write scope.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Required | HTTPS webhook endpoint URL (no private IPs or localhost) |
| events | string[] | Optional | Event 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
/webhooksList registered webhook endpoints. Requires API Key auth with webhooks:read scope.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| cursor | uuid | Optional | Pagination cursor from previous response |
| limit | integer | Optional | Results per page (1-100, default 25) |
Request
GET /api/v1/webhooks?limit=25200Paginated 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.
| Event | Description |
|---|---|
| license.created | A new license was created |
| license.updated | A license was updated |
| license.suspended | A license was suspended |
| license.reinstated | A suspended license was reinstated |
| license.revoked | A license was permanently revoked |
| license.renewed | A license expiration was extended |
| license.expired | A license reached its expiration date |
| license.deleted | A license was soft-deleted |
| machine.activated | A machine was activated |
| machine.deactivated | A machine was deactivated |
| machine.heartbeat | A machine heartbeat was received |
| machine.dead | A machine missed its heartbeat window |
| validation.success | A license validation succeeded |
| validation.failed | A 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=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2Node.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.