Docs
ExtensionsWebhooks

Overview

POST licensing events to your own endpoints or a Discord channel in real time.

Webhooks push events to a URL you own so you can react in real time: revoke a seat when a license expires, alert a channel when a key is rejected, or sync a record on a new activation.

This extension uses core when present. See the Configuration for the settings and the API Reference for the endpoints. The delivery payload and signature are described below.

How it works

  • Add a webhook from the Webhooks dashboard page (or the API) with a target URL and the events you want.
  • Kora POSTs each event as JSON, signed with an x-kora-signature HMAC over the raw body.
  • Return any 2xx to acknowledge. On a bad status or timeout Kora retries, then records the failure.
  • Every attempt is kept in the deliveries log. You can rotate the secret or send a test event anytime.

The delivery

Each event is a JSON POST to your URL:

{
  "event": "license.validated",
  "timestamp": 1700000000000,
  "data": { "license": "LICENSE-KEY", "product": "Your Product", "customer": "Some Customer" }
}
HeaderValue
x-kora-eventThe event name, for routing without parsing the body.
x-kora-signatureHMAC-SHA256 of the raw body, signed with the webhook secret.

Anyone can POST to your URL, so verify every delivery: recompute the signature over the raw body (the bytes must match, not a re-serialized object) and compare it in constant time.

import crypto from "crypto";

const expected = crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");
const signature = request.header("x-kora-signature") ?? "";
const ok = expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

Discord

Paste a Discord webhook URL as the target and Kora sends each event as a color-coded embed, with no relay or signature handling on your side.

Events

license.created, license.updated, license.deleted, license.validated, license.rejected, license.expired, session.opened, session.closed, session.limit_exceeded. Leave a webhook's event list empty to receive all of them.

On this page