Webhooks
Webhooks let your server receive an HTTP POST when something happens in your Lizen account — no polling required.
Available on Hobby tier and above.
Payload envelope
All webhook payloads follow the same structure:
{ "id": "evt_01j9abc123", "type": "key.revoked", "created_at": "2026-08-16T12:00:00Z", "account_id": "acc_xyz789", "data": { "id": "lic_abc123", "key_prefix": "MYAPP-ABCD", "product_id": "prod_def456", "status": "revoked" }}Verifying signatures
Every webhook request includes an X-Lizen-Signature header — an HMAC-SHA256 hex digest of the raw request body, signed with your webhook’s secret.
import { createHmac } from 'crypto';
function verifyWebhook(body: string, signature: string, secret: string): boolean { const expected = createHmac('sha256', secret) .update(body, 'utf8') .digest('hex'); return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));}Delivery and retries
Lizen delivers webhooks asynchronously via Cloudflare Queues. Delivery is attempted up to 6 times with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 5 seconds |
| 3 | 30 seconds |
| 4 | 2 minutes |
| 5 | 10 minutes |
| 6 | 30 minutes |
A delivery is considered successful when your server returns any 2xx status. After 6 failed attempts, the event is dropped and logged.
Best practices
- Respond with
200immediately, then process the event asynchronously. If your handler is slow, webhooks will time out. - Use the
idfield for idempotency — the same event may be delivered more than once on retries. - Check
created_atto handle events that arrive out of order. - Store the webhook secret in an environment variable, never in source code.
Event types
| Event | When it fires |
|---|---|
key.created | A new license key is generated |
key.revoked | A license key is revoked |
key.expired | A license key’s expiry passes (daily cron) |
key.extended | A license key’s expiry is extended |
activation.created | A new device activates a license |
activation.deactivated | A device activation is removed |
product.created | A new product is created |
account.plan_changed | The account plan changes |