Documentation
Webhooks
KeyStack pushes events to your URL whenever something interesting happens to a license or an order. Use these to keep your own systems in sync — fire a welcome email, update your CRM, message your team in Slack.
Setup
In the dashboard go to Settings → Webhooks → Add endpoint. You'll get a signing secret (whsec_...) — save it, you'll see it once.
Event envelope
Every event ships with the same envelope:
{
"id": "evt_01HABCDEF",
"type": "license.created",
"createdAt": "2026-05-15T20:00:00.000Z",
"organizationId": "org_01H...",
"applicationId": "app_01H...",
"data": { /* type-specific */ }
}Signature verification
Each request carries X-KeyStack-Signature: t=<timestamp>,v1=<hex>. Verify with:
import crypto from 'node:crypto';
function verify(secret: string, header: string, rawBody: string): boolean {
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=') as [string, string]),
);
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(parts.v1, 'hex'),
);
}Respond with 2xx within 5 seconds to acknowledge. Anything else is treated as a failure and retried with exponential back-off (5s, 30s, 5m, 30m, 2h, 6h, then daily for up to 5 days).
Supported events
| Event | Fires when |
|---|---|
license.created | A new license is issued via dashboard, API or Stripe webhook. |
license.updated | Status, expiry or metadata changes. |
license.activated | First successful validation with a new fingerprint. |
license.deactivated | An activation is removed. |
license.frozen | Status changes to FROZEN. |
license.expired | Crosses the expiresAt boundary. |
license.revoked | Hard-revoked. |
order.created | A Stripe order is mirrored into KeyStack. |
order.refunded | Stripe reports a refund. |
customer.created | A new customer is auto-created on first license. |
Testing
The dashboard has a Replay button next to every delivery. Click it to fire the exact same payload + signature at your endpoint as many times as you need.