KeyPort LogoKeyPort
Back to all guides
Security• April 2026

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.

AK
Alex KimAuthor • 5 min read

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

EventWhen it fires
license.createdNew license is issued
license.revokedLicense is manually revoked
license.expiredLicense passes its expiry date
license.ip_blockedValidation blocked by IP limit
license.ip_removedIP removed from license
license.ips_resetLicense IPs reset
license.transferredLicense assigned to new customer
license.transfer_rejectedLicense transfer rejected
license.bulk_revokedMultiple 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 verify

Common 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

Scale your product with KeyPort

Free tier available for launch and small production workloads. No credit card required.