Webhook events
Each webhook event KeyGrant sends: when it is sent, and what its data holds (see
The request).
| Event | Sent when |
|---|---|
license.issued | A key was created. |
license.revoked | A key was revoked, or its subscription canceled. |
license.reinstated | A revoked key works again. |
license.upgraded | A key got an upgrade. |
license.upgrade_reverted | A refund or a lost dispute took an upgrade back. |
license.flagged | A purchase is flagged for you to look at. |
license.expiring | A subscription or timed key expires within 14 days. |
license.picked_up | An app restored a key by key pickup. |
trial.ending | A trial key has 3 days or fewer before it expires. |
device.activated | A device took a seat on a key. |
device.released | A seat was released. |
device.limit_reached | A device's activation was refused because every seat was taken. |
device.rekeyed | A seat moved from a host name to a machine ID. |
device.shared | A seat was seen on two or more machines. |
meter.used_up | A meter has no uses left. |
meter.overage | A device back from offline spent past a meter. |
sale.held | An upgrade or a timed model's sale changed no key and is held. |
Common fields
These mean the same in every event that carries them. Times are in Unix milliseconds.
The license key, as issued.
The product's API ID.
The key's email address, or null when the key has none.
The number of whole days left, rounded up. At least 1.
License events
license.issued
Sent when
Issued in the dashboard or over the API, imported, or sold through a Stripe or Paddle checkout.
Data
Common fields: key, product, email, and:
The key's type: perpetual, subscription, trial or timed.
The key's license model, or null.
dashboard, api, import or checkout.
When the key expires, or null.
A timed key still waiting for its first activation: its { months, days }. Otherwise null.
Only on a key that is one of a sale of several keys: { id, index, size }, so each key's event says which sale it is part of. There is no event for the sale itself.
A key sent as a file (a key file or a license file) adds file: { id, kind, filename, mediaType }, never the file itself; fetch it with POST /v1/licenses/:key/file.
Notes
- Not sent when a checkout upgrades a key the customer already holds (
license.upgradedis).
license.revoked
Sent when
A key was revoked, or its Stripe or Paddle subscription was canceled:
- By hand or over the API.
- By a full refund or a lost dispute (see Refunds and disputes).
- Its Stripe or Paddle subscription was canceled.
- Up to a day after a subscription key's grace period ends or a trial key expires.
Data
Common fields: key, product, and:
The key's email address, or null. Absent on the event sent after a grace period or a trial's expiry.
manual, refund, chargeback or subscription_canceled. For a trial key that simply expired, reason is manual.
Only on a cancellation: when the key will stop working, its expiry plus its grace period.
true, only on the event sent after a grace period or a trial's expiry.
Notes
- A canceled subscription therefore sends it twice: once when it is canceled, and again when the key stops working.
license.reinstated
Sent when
In any of these cases:
- A subscription key that was canceled, or revoked after its grace period ran out, because Stripe or Paddle reports its subscription as paid. A key revoked by hand, by a refund or by a dispute is never reinstated this way.
- A refund that revoked a key fails or is canceled after Stripe took it (see A refund that fails); a failed refund never brings back a key revoked by hand or by a dispute.
- A chargeback Paddle reverses puts back the keys it revoked (see Selling through Paddle).
- You put a revoked key back by hand.
Data
Common fields: key, product, email, and:
The key's expiry.
A key sent as a license file that a refund or a dispute had revoked adds withdrawnFileIds: its own file ids, which leave that flag as the key comes back and which no update of your app should refuse now.
license.upgraded
Sent when
A checkout upgraded a key the customer already holds, or an upgrade was put on a key afterwards:
- A version upgrade added versions (see Sell an upgrade).
- An edition upgrade changed its entitlements (see Sell an edition upgrade).
- A device upgrade raised its device limit (see Sell more devices).
- A top-up added uses to its meters (see Sell more uses).
- A timed model bought again extended its term, one event for each key of its purchase (see Timed models).
- You attached a held sale to a key (see Resolve a held sale).
- A refund that took an upgrade back failed, and the upgrade was put back on the key.
Data
Common fields: key, product, email, and:
What changed: versions, entitlements, devices, uses or term.
The Checkout Session ID, or null.
As the key now stands: minMajor, maxMajor (null for "and later") and upgradeUntil.
As the key now stands.
The key's meters as the license object shows them, {} for none.
A timed key still waiting for its first activation: its { months, days }. Otherwise null.
license.upgrade_reverted
Sent when
A full refund or a lost dispute took such an upgrade back (see Refunds and disputes). The key stays active.
Data
The same fields as license.upgraded, the key as it stands after.
refund or chargeback.
Only when a top-up is taken back, which takes back only the uses not yet spent: by meter code, each { added, takenBack, kept }, kept being what was already spent.
license.flagged
Sent when
KeyGrant flags a purchase for you to look at (see Repeat refunds and duplicate purchases). The kind says why:
repeat-refund: a checkout created a key whose buyer holds an earlier key of the same product, for the same major version, that a refund or a dispute took back.duplicate: a checkout created a key whose buyer holds an earlier key of the same product, for the same major version, bought under the same license model in the 7 days before.refunded-file: a refund, a lost dispute, or a revoke whose reason isrefundorchargeback, revokes keys sent as license files, which keep working offline.
Data
Common fields: product, email, and:
The flag's ID.
The purchase's first key.
repeat-refund, duplicate or refunded-file.
The keys it was flagged against, newest first.
{ id, size } for a sale of several keys, or null. For a refunded-file flag, sale.size is the number of keys that refund revoked.
When it was flagged. Sent again, its flaggedAt is when it was flagged again.
Only on a refunded-file flag: the ids to list in your app's revokedLicenseFiles in its next update.
Notes
- For
repeat-refundandduplicate, a key not yet bound to a major version counts as any. The key was created as usual, andlicense.issuedwas sent first. One per kind. - One flag per purchase: while it still names a file, a key of it refunded again, or another key of the sale refunded later, adds its ids, and the flag is sent again under the same
id, itsfileIdsevery id it names now. - A flag whose keys were all put back is gone, so a later refund usually sends a new flag with a new
id.
A license.flagged event's data:
{ "id": "flg_4f1c9a0e2b7d4c6f8a1b3e5d7c9f0a2b", "key": "SLUICE-7K2M-Q9XD-4HTR-WB6N", "product": "sluice", "kind": "repeat-refund", "earlierKeys": ["SLUICE-3HVW-7RNC-LD2B-M6PQ"], "sale": null, "email": "[email protected]", "flaggedAt": 1791504000000} license.expiring
Sent when
A subscription key that is not canceled, or a timed key, has 14 days or fewer before it expires.
Data
Common fields: key, product, email, days, and:
subscription or timed.
When the key expires.
Notes
- Sent once per key for each expiry date.
license.picked_up
Sent when
An app restored a customer's keys by key pickup: the customer typed the code KeyGrant emailed to the address they bought with, and KeyGrant handed that address's keys of the product to the app.
Data
Common fields: key, product, and:
The address the customer gave.
How many keys the same pickup handed out.
Notes
- One event for each key handed out.
- The app then activates one of them as usual, which sends
device.activated.
trial.ending
Sent when
A trial key has 3 days or fewer before it expires.
Data
Common fields: key, product, email, days, and:
When the key expires.
Notes
- Sent once per key for each expiry date.
- Not sent for device trials, which have no key.
Device events
device.activated
Sent when
A device took a seat on a key.
Data
Common fields: key, product, and:
The seat it took.
The device's fingerprint.
Notes
- Not sent again when a device that already holds a seat activates again.
device.released
Sent when
In any of these cases:
- By the app, in the dashboard or over the API.
- By your customer in the customer portal.
- By KeyGrant, for a device that went past its license's days without checking in (see Free unused seats).
Data
Common fields: key, product, and:
The seat, and its device's fingerprint.
Only on a seat KeyGrant freed: inactive.
Only on a seat KeyGrant freed: the device's last check-in.
Notes
- The events for seats KeyGrant freed go out within minutes of the nightly pass that frees the seats.
A device.released event's data when KeyGrant freed the seat:
{ "key": "SLUICE-7K2M-Q9XD-4HTR-WB6N", "product": "sluice", "activationId": "act_3f9c2a71d04b4e8a9c6d1e2f", "fingerprint": "5e1f0ca7d00d4a37b9c2", "reason": "inactive", "lastSeen": 1788912000000} device.limit_reached
Sent when
A device's activation was refused because every seat was taken.
Data
Common fields: key, product, and:
The key's device limit.
device.rekeyed
Sent when
A seat activated under its device's host name moved to the device's machine ID.
Data
Common fields: key, product, and:
The seat.
The two fingerprints' hashes, never the fingerprints.
Notes
- Sent once per seat.
device.shared
Sent when
A seat was seen on two or more machines within 30 days (see Device management).
Data
Common fields: key, product, and:
The seat.
How many machines it was seen on, up to 20.
How many of those machines were never accepted.
Notes
- Sent at once the first time, then at most once an hour per seat when the count has grown.
Meter events
For a key whose license model counts uses (see Metered uses). They are sent in the background and never fail a spend or a check-in.
meter.used_up
Sent when
A spend, a report of what a device spent offline, or a spend from your backend took its last use.
Data
Common fields: key, product, email, and:
The meter's code.
The meter's uses with its extra, and what was used.
The end of the billing period, or null.
The device that spent them, or null for a spend from your backend.
app, report or api.
Notes
- Sent once each time a meter goes from having uses to having none, so a top-up, an add, a reset or an edit that gives it uses again lets it be sent again.
- This is the moment to offer a top-up. KeyGrant sends the customer no such nudge.
meter.overage
Sent when
Its report of spends took a meter past what the key holds. The device spent from its offline allowance while KeyGrant could not be reached, and was never refused for it.
Data
Common fields: key, product, email, and:
The meter's code.
The meter's uses with its extra, and what was used.
How far over its uses the meter now stands, all of it, not what this report added.
The uses this report counted toward the meter.
The device's offline allowance.
The device that reported.
The end of the billing period, or null.
Notes
- Sent once for each report that grew the overage, which is at most one a check-in.
Sale events
sale.held
Sent when
A paid upgrade sale, or a timed model's purchase, changed no key: KeyGrant held it, and it waits for you to attach it to a key or refund it. Each kind's causes are linked from Held sales.
Data
Common fields: product, and:
The held sale's ID.
The license model sold.
The Checkout Session ID and the Stripe payment; for a Paddle sale, the transaction ID (txn_…) in both.
The buyer's address and their customer at Stripe or Paddle.
Why it was held:
no-key: no key it can change was found.several-keys: more than one key could take it, and the checkout named none.already-upgraded: the key already holds what it sells.not-eligible: the key found or named cannot take it.quantity: how many it bought does not fit the key's seats.basket: it was bought with other items, so its line cannot be told.lines-unread: Stripe or Paddle would not give the checkout's lines.over-cap: the devices it adds would take the key past 1,000.ended: a timed model's date had passed.
What it sells: entitlements, versions, devices, uses or term.
An edition upgrade's entitlements: the set a key must hold, and the set that replaces it.
How many it bought at once.
false when the checkout did not say how many it bought: quantity, and devices, are then one unit's worth.
The seats of the key it found, as the reason quantity compares them.
What it charged, in minor units, and its currency.
A version upgrade's sold versions (minMajor, maxMajor, null for "and later", upgradeUntil), or null.
What a device upgrade adds in all, or null.
What a top-up adds in all, by meter code, or null.
What a timed sale adds, { months }, { days } or { endsAt }, with keys when one purchase sells several, or null.
Notes
- No key exists for it, so it carries none.
What sends no event
- An ordinary subscription renewal.
- A failed renewal.
- An extension.
- A change of versions made by hand or over the API.
- A change of entitlements made by hand or over the API.
- A change of meters made by hand or over the API.
- A device trial.