Webhooks Documentation

Complete guide to implementing and using webhooks with real-time notifications

Overview

Webhooks allow you to receive real-time HTTP notifications when specific events occur in your license management system. Instead of polling the API repeatedly, webhooks push data to your server instantly when events happen.

Benefits

  • • Real-time notifications when licenses are validated, activated, or expire
  • • Reduced API calls - no need to poll continuously
  • • Automation - trigger workflows in your systems based on license events
  • • Scalability - handle high-volume validation without performance impact
  • • Integrations - connect to third-party services (Slack, Discord, CRM)

Plan Availability

FeatureFreeStarterProfessionalEnterprise
Max Licenses510100Unlimited
Webhooks✅ (10 max)✅ (50 max)
Event Types--All eventsAll events
Delivery Logs--30 days90 days

Getting Started

1. Create a Webhook Endpoint

First, create an HTTP endpoint on your server that can receive POST requests:

// Node.js/Express example
app.post('/webhooks/license-validator', (req, res) => {
  const event = req.body;

  console.log('Received event:', event.event);
  console.log('Data:', event.data);

  // Process the event
  // ... your business logic here

  // Return 200 OK to acknowledge receipt
  res.status(200).send('OK');
});

Your endpoint must:

  • • Accept POST requests
  • • Be publicly accessible via HTTPS
  • • Respond with HTTP 200-299 status code
  • • Respond within 10 seconds
  • • Verify webhook signatures (see Security section)

2. Register Your Webhook

  1. 1Navigate to the Webhooks page in your dashboard
  2. 2Click "Create Webhook"
  3. 3Enter your webhook URL (must be HTTPS)
  4. 4Select which events you want to receive
  5. 5Save - you'll receive a webhook secret (save it securely!)

3. Test Your Webhook

Use the "Test" button in the dashboard to send a test event and verify your endpoint is working.

Event Types

license.validated

Fires every time the /api/validate endpoint successfully validates a license.

When it fires:

  • • License key is valid
  • • License is active (not disabled)
  • • License has not expired
  • • Device fingerprint matches OR max activations not reached

Example payload:

{
  "event": "license.validated",
  "timestamp": "2025-01-03T14:25:30.000Z",
  "data": {
    "licenseId": "lic_xyz789",
    "licenseKey": "ABCD-1234-EFGH-5678",
    "licenseName": "Production License",
    "fingerprint": "device-abc-123",
    "deviceName": "MacBook Pro",
    "deviceType": "desktop",
    "ipAddress": "192.168.1.100",
    "currentActivations": 3,
    "maxActivations": 5
  }
}

license.activated

Fires when a new device activates a license for the first time.

When it fires:

  • • New device fingerprint validates for the first time
  • • Activation record is created
  • • License is successfully activated

Use cases:

  • • Send welcome emails to new device users
  • • Alert when activation limit is approaching
  • • Notify sales team of expansion usage

license.expired

Fires when a license reaches its expiration date.

Use cases:

  • • Send renewal reminder emails
  • • Create renewal opportunities in CRM
  • • Notify account managers
  • • Trigger automated renewal workflows

license.disabled

Fires when a license is manually disabled by a user.

Use cases:

  • • Audit license management actions
  • • Notify customers of license changes
  • • Track license churn

Security & Signature Verification

⚠️ Always verify webhook signatures! This prevents attackers from sending fake webhook events to your server.

Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature.

Node.js / Express

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(payload).digest('hex');

  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = req.body.toString('utf8');
  const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const event = JSON.parse(payload);
  res.status(200).send('OK');
});

Python / Flask

import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_signature, signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature')
    payload = request.get_data(as_text=True)

    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        abort(401)

    event = request.get_json()
    return 'OK', 200

PHP

<?php
function verifyWebhookSignature($payload, $signature, $secret) {
    $expectedSignature = hash_hmac('sha256', $payload, $secret);
    return hash_equals($expectedSignature, $signature);
}

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

if (!verifyWebhookSignature($payload, $signature, $webhookSecret)) {
    http_response_code(401);
    die('Invalid signature');
}

http_response_code(200);
echo 'OK';
?>

Retry Logic

If your webhook endpoint fails or times out, we automatically retry delivery with exponential backoff.

AttemptDelayWhen
1ImmediateInitial delivery
21 minuteAfter first failure
35 minutesAfter second failure
430 minutesFinal attempt

Timeout

Your endpoint has 10 seconds to respond. For long-running operations, return 200 OK immediately and process asynchronously.

Testing Webhooks

Using the Dashboard

  1. 1. Go to the Webhooks page
  2. 2. Click the "Test" button next to your webhook
  3. 3. A test event will be sent immediately
  4. 4. Check the response and your server logs

Local Testing with ngrok

To test webhooks on your local development machine:

# 1. Start your local server
node server.js
# Server running on http://localhost:3000

# 2. Start ngrok tunnel
ngrok http 3000

# 3. Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
# 4. Create webhook with: https://abc123.ngrok.io/webhook
# 5. View requests at: http://127.0.0.1:4040

Integration Examples

Send Slack Notifications

const { WebClient } = require('@slack/web-api');
const slack = new WebClient(process.env.SLACK_BOT_TOKEN);

async function handleLicenseActivated(data) {
  await slack.chat.postMessage({
    channel: '#license-alerts',
    text: `🎉 New license activation!`,
    blocks: [{
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `*New Device Activated*\n\n*License:* ${data.licenseName}\n*Customer:* ${data.customerName}\n*Device:* ${data.deviceName}`
      }
    }]
  });
}

Send Email with SendGrid

const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

async function handleLicenseExpired(data) {
  await sgMail.send({
    to: data.customerEmail,
    from: 'noreply@yourdomain.com',
    subject: 'License Expiration Notice',
    html: `<p>Your license <strong>${data.licenseName}</strong> has expired.</p>`
  });
}

Log to Database

const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

async function handleLicenseValidated(data) {
  await pool.query(
    `INSERT INTO license_events (event_type, license_id, device_fingerprint, timestamp)
     VALUES ($1, $2, $3, $4)`,
    ['validated', data.licenseId, data.fingerprint, new Date()]
  );
}

Troubleshooting

Webhook Not Firing

  • Check webhook is enabled (Status: Active)
  • Verify event subscriptions are correct
  • Ensure you have Professional or Enterprise plan
  • Test the webhook using the Test button
  • Review webhook logs for delivery attempts

Signature Verification Failing

  • Ensure you're using the correct webhook secret
  • Use raw JSON body (not parsed object) for verification
  • Verify you're using HMAC-SHA256 algorithm
  • Use timing-safe comparison functions
  • Check for UTF-8 encoding issues

Webhook Timing Out

  • Respond with 200 OK within 10 seconds
  • Process webhook data asynchronously
  • Avoid slow database queries before responding
  • Don't wait for external API calls
  • Use background jobs for long operations

Best Practices

✅ DO

  • • Always verify webhook signatures
  • • Respond with 200 OK immediately
  • • Process data asynchronously
  • • Implement idempotency with deliveryId
  • • Log all webhook deliveries
  • • Use HTTPS for your endpoint
  • • Monitor webhook health metrics
  • • Handle errors gracefully

❌ DON'T

  • • Skip signature verification
  • • Process synchronously before responding
  • • Use HTTP (always use HTTPS)
  • • Hardcode webhook secrets
  • • Log sensitive data
  • • Let webhook errors crash your app
  • • Deploy untested webhook changes
  • • Ignore duplicate events

API Reference

Create Webhook

POST/api/webhooks

Authentication: Required (Professional/Enterprise only)

// Request
{
  "url": "https://yourdomain.com/webhook",
  "events": ["license.validated", "license.activated"],
  "isActive": true
}

// Response (201)
{
  "id": "webhook_abc123",
  "secret": "whsec_def456...", // Only shown once!
  "url": "https://yourdomain.com/webhook",
  "events": ["license.validated", "license.activated"],
  "isActive": true
}

Test Webhook

POST/api/webhooks/test
// Request
{
  "webhookId": "webhook_abc123"
}

// Response (200)
{
  "success": true,
  "deliveryId": "delivery_test_xyz789",
  "statusCode": 200,
  "responseTime": 123
}