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
| Feature | Free | Starter | Professional | Enterprise |
|---|---|---|---|---|
| Max Licenses | 5 | 10 | 100 | Unlimited |
| Webhooks | ❌ | ❌ | ✅ (10 max) | ✅ (50 max) |
| Event Types | - | - | All events | All events |
| Delivery Logs | - | - | 30 days | 90 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
- 1Navigate to the Webhooks page in your dashboard
- 2Click "Create Webhook"
- 3Enter your webhook URL (must be HTTPS)
- 4Select which events you want to receive
- 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', 200PHP
<?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.
| Attempt | Delay | When |
|---|---|---|
| 1 | Immediate | Initial delivery |
| 2 | 1 minute | After first failure |
| 3 | 5 minutes | After second failure |
| 4 | 30 minutes | Final 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. Go to the Webhooks page
- 2. Click the "Test" button next to your webhook
- 3. A test event will be sent immediately
- 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
/api/webhooksAuthentication: 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
/api/webhooks/test// Request
{
"webhookId": "webhook_abc123"
}
// Response (200)
{
"success": true,
"deliveryId": "delivery_test_xyz789",
"statusCode": 200,
"responseTime": 123
}