Webhook-Based License Events — Build Real-Time License Monitoring
How to use webhook events to build real-time monitoring for license activations, expirations, IP-blocked attempts, and more.
Webhooks turn your license management system from a passive database into a live event stream. Instead of polling for what happened, your system gets notified the instant something does. This guide shows how to build real-time license monitoring using KeyPort webhooks.
Why License Webhooks Matter
Without webhooks, you only know about events when you check the dashboard. With webhooks:
- License created → email a welcome and setup guide immediately
- License expires → start a renewal campaign automatically
- IP blocked → alert your fraud team in real time
- License revoked → update CRM and billing system
Webhooks are a Pro+ feature in KeyPort, configured per product.
Events KeyPort Sends
| Event | When it fires |
|---|---|
license.created | New license is issued |
license.revoked | License is manually revoked |
license.expired | License passes its expiry date |
license.ip_blocked | Validation blocked by IP limit |
license.ip_removed | IP removed from license |
license.ips_reset | License IPs reset |
license.transferred | License assigned to new customer |
license.transfer_rejected | License transfer rejected |
license.bulk_revoked | Multiple licenses revoked at once |
Setting Up Your Webhook Endpoint
Always verify the HMAC-SHA256 signature before processing. Always respond with 200 immediately — don't await slow processing.
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.raw({ type: 'application/json' })); // raw body required
app.post('/webhooks/keyport', (req, res) => {
const sig = req.headers['x-keyport-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.KEYPORT_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (sig !== expected) return res.status(401).send('Unauthorized');
res.status(200).send('ok'); // respond immediately
handleEvent(JSON.parse(req.body.toString())).catch(console.error);
});
async function handleEvent({ type, data }) {
switch (type) {
case 'license.expired':
await startRenewalSequence(data.license.customer_email);
break;
case 'license.ip_blocked':
await notifyFraudTeam({ key: data.license.key, ip: data.ip_address });
break;
case 'license.revoked':
await updateCRM(data.license.customer_email, { status: 'revoked' });
break;
}
}Idempotency — Handling Retries Safely
KeyPort retries on failure: 1 min → 5 min → 30 min. Your handler must be idempotent — processing the same event twice must have the same result as processing it once.
// Store events with ON CONFLICT DO NOTHING to deduplicate
await db.query(
'INSERT INTO license_events (event_id, event_type, license_id, occurred_at) VALUES ($1, $2, $3, now()) ON CONFLICT (event_id) DO NOTHING',
[event.id, event.type, event.data.license.id]
);Testing Locally
# Expose local port 3000 with ngrok
ngrok http 3000
# → https://abc123.ngrok.io/webhooks/keyport
# Paste this URL in KeyPort product webhook settings
# Trigger test events from the dashboard to verifyCommon Automation Patterns
- license.created → onboarding email: Send setup instructions and docs link when a new license is issued
- license.expired → renewal drip: Day 0, Day 3, Day 7 sequence
- license.ip_blocked → customer alert: Tell the customer their access was blocked from an unusual location
- license.revoked → access audit: Log revocation reason and update customer record