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

EventFires when
license.createdA new license is issued via dashboard, API or Stripe webhook.
license.updatedStatus, expiry or metadata changes.
license.activatedFirst successful validation with a new fingerprint.
license.deactivatedAn activation is removed.
license.frozenStatus changes to FROZEN.
license.expiredCrosses the expiresAt boundary.
license.revokedHard-revoked.
order.createdA Stripe order is mirrored into KeyStack.
order.refundedStripe reports a refund.
customer.createdA 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.