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:

EventFired when
license.createdA new license is created
license.updatedA license is modified (name, expiry, activations)
license.deletedA license is permanently deleted
license.expiredA license status is set to expired
license.suspendedA license is suspended
license.revokedA license is permanently revoked
license.validatedA license is validated (cache-miss only to reduce volume)
license.hwid_resetHardware bindings are reset for a license

Payload Format

All webhook deliveries are POST requests with a JSON body:

Webhook payloadjson
{
  "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

HeaderDescription
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 signature for verification
X-Webhook-EventThe event type (e.g. license.created)
User-AgentOnyxAuth-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").

JavaScriptjavascript
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

POST/api/v1/webhooks
Request bodyjson
{
  "url": "https://example.com/webhooks/licenses",
  "events": ["license.created", "license.expired", "license.revoked"],
  "projectId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "description": "Production webhook"
}

Parameters

FieldTypeRequiredDescription
urlstringYesPublic URL to receive POST requests (HTTPS recommended)
eventsstring[]YesArray of event types to subscribe to
projectIdstringNoScope webhook to a specific project
descriptionstringNoHuman-readable label, max 255 chars
201 Createdjson
{
  "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

GET/api/v1/webhooks
200 OKjson
{
  "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

PATCH/api/v1/webhooks/:id
Request bodyjson
{
  "events": ["license.created", "license.deleted"],
  "active": false
}

Delete Webhook

DELETE/api/v1/webhooks/:id

Test Webhook

POST/api/v1/webhooks/:id/test

Sends a webhook.test event to verify your endpoint is reachable and correctly verifying signatures.

Test payloadjson
{
  "event": "webhook.test",
  "timestamp": "2026-02-12T15:30:00.000Z",
  "data": {
    "message": "This is a test webhook delivery from OnyxAuth."
  }
}

Rotate Secret

POST/api/v1/webhooks/:id/rotate-secret

Generates a new signing secret for the webhook. The old secret stops working immediately. The new secret is returned once, so save it.

200 OKjson
{
  "success": true,
  "data": {
    "secret": "whsec_new_secret_here..."
  }
}

Delivery History

GET/api/v1/webhooks/:id/deliveries

Returns the 20 most recent delivery attempts for a webhook, including status codes and response times.

200 OKjson
{
  "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-Signature header before processing
  • Respond with 2xx quickly and do heavy processing asynchronously
  • Make your webhook handler idempotent since the same event may be delivered more than once
  • Use the timestamp field 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