Webhooks
Receive real-time HTTP notifications when license events occur. Webhooks let you build integrations that react to license changes, like syncing with your CRM, sending notifications, or triggering automated workflows.
Events
Subscribe to any combination of these events when creating a webhook:
| Event | Fired when |
|---|---|
| license.created | A new license is created |
| license.updated | A license is modified (name, expiry, activations) |
| license.deleted | A license is permanently deleted |
| license.expired | A license status is set to expired |
| license.suspended | A license is suspended |
| license.revoked | A license is permanently revoked |
| license.validated | A license is validated (cache-miss only to reduce volume) |
| license.hwid_reset | Hardware bindings are reset for a license |
Payload Format
All webhook deliveries are POST requests with a JSON body:
{
"event": "license.created",
"timestamp": "2026-02-12T15:30:00.000Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"key": "A3F9-KM7X-P2VB-8NQT",
"productName": "My App",
"status": "active",
"maxActivations": 1,
"expiresAt": "2026-12-31T23:59:59.000Z"
}
}Headers
| Header | Description |
|---|---|
| Content-Type | application/json |
| X-Webhook-Signature | HMAC-SHA256 signature for verification |
| X-Webhook-Event | The event type (e.g. license.created) |
| User-Agent | OnyxAuth-Webhooks/1.0 |
Verifying Signatures
Every webhook delivery includes an X-Webhook-Signature header signed with your webhook's secret. Always verify signatures before processing events.
The signature format is t=timestamp,v1=hash. The hash is computed as HMAC-SHA256(secret, "timestamp.body").
import crypto from 'crypto';
function verifyWebhookSignature(body, signature, secret) {
const [tPart, vPart] = signature.split(',');
const timestamp = tPart.replace('t=', '');
const hash = vPart.replace('v1=', '');
// Reject old timestamps (>5 minutes) to prevent replay attacks
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
if (age > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(hash)
);
}
// Express/Node.js handler
app.post('/webhooks/onyxauth', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const rawBody = req.body; // Use raw body string
if (!verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(rawBody);
console.log('Received event:', event.event, event.data);
res.status(200).json({ received: true });
});Managing Webhooks
Create and manage webhooks from your dashboard or via the API. All endpoints require session authentication. Creating webhooks requires a Developer plan.
Create Webhook
/api/v1/webhooks{
"url": "https://example.com/webhooks/licenses",
"events": ["license.created", "license.expired", "license.revoked"],
"projectId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"description": "Production webhook"
}Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Public URL to receive POST requests (HTTPS recommended) |
| events | string[] | Yes | Array of event types to subscribe to |
| projectId | string | No | Scope webhook to a specific project |
| description | string | No | Human-readable label, max 255 chars |
{
"success": true,
"data": {
"id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
"url": "https://example.com/webhooks/licenses",
"events": ["license.created", "license.expired", "license.revoked"],
"active": true,
"secret": "whsec_a1b2c3d4e5f6..."
}
}The secret is only returned on creation. Store it securely since you'll need it to verify webhook signatures.
List Webhooks
/api/v1/webhooks{
"success": true,
"data": [
{
"id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
"url": "https://example.com/webhooks/licenses",
"events": ["license.created", "license.expired"],
"active": true,
"description": "Production webhook",
"createdAt": "2026-02-12T10:00:00.000Z"
}
]
}Update Webhook
/api/v1/webhooks/:id{
"events": ["license.created", "license.deleted"],
"active": false
}Delete Webhook
/api/v1/webhooks/:idTest Webhook
/api/v1/webhooks/:id/testSends a webhook.test event to verify your endpoint is reachable and correctly verifying signatures.
{
"event": "webhook.test",
"timestamp": "2026-02-12T15:30:00.000Z",
"data": {
"message": "This is a test webhook delivery from OnyxAuth."
}
}Rotate Secret
/api/v1/webhooks/:id/rotate-secretGenerates a new signing secret for the webhook. The old secret stops working immediately. The new secret is returned once, so save it.
{
"success": true,
"data": {
"secret": "whsec_new_secret_here..."
}
}Delivery History
/api/v1/webhooks/:id/deliveriesReturns the 20 most recent delivery attempts for a webhook, including status codes and response times.
{
"success": true,
"data": [
{
"id": "...",
"eventType": "license.created",
"statusCode": 200,
"success": true,
"attempts": 1,
"createdAt": "2026-02-12T15:30:00.000Z"
}
]
}Best Practices
- •Always verify the
X-Webhook-Signatureheader before processing - •Respond with
2xxquickly and do heavy processing asynchronously - •Make your webhook handler idempotent since the same event may be delivered more than once
- •Use the
timestampfield in the payload for deduplication - •Reject signatures older than 5 minutes to prevent replay attacks
- •Rotate your webhook secret periodically via the dashboard or API
- •Use the test endpoint to verify your integration before going live