Skip to content

Store webhooks

A store product turns a store’s delivery webhook into keys. Each verified call generates keys for one tier and duration and answers in the format the store shows to the buyer. No token is needed: the webhook is signed instead.

  1. In the panel, open the app and go to Seller API & stores → Add product (see Seller API & stores).
  2. Pick the provider, tier, duration, HWID resets, max quantity and optional key mask.
  3. For Sell.App and Sellix, paste the store’s webhook secret. For Generic, copy the secret the panel generates (Webhook signing secret — shown once).
  4. Copy the webhook URL and use it for that one product in the store:
https://panel.eonauth.fyi/hooks/store/store_…

Use one store product per product you sell. A product that sells Pro, 1 month and another that sells Pro, lifetime need two store products.

Provider Signature header Signed over Answer
Sell.App Signature: hex HMAC-SHA256 the raw body {"message": "KEY1\nKEY2"}
Sellix X-Sellix-Signature: hex HMAC-SHA512 the raw body the keys as plain text, one per line
Generic X-EonAuth-Signature: hex HMAC-SHA256, plus X-EonAuth-Timestamp timestamp + "." + raw body {"keys": ["KEY1", "KEY2"]}

For Sell.App and Sellix, the store computes the signature with the secret it gave you; there is nothing to code. Point the product’s dynamic delivery at the URL.

The number of keys is the first quantity field found anywhere in the JSON body (a number or a string of digits), or 1 if there is none. It must be between 1 and the product’s max quantity (default 10, up to 100); otherwise the call fails with 400 and Quantity 12 is outside 1-10 for this product.

Stores retry when a delivery seems to fail. A request with the same body gets the same keys back, for 7 days, instead of new ones. Two copies of a request arriving at once still produce one set of keys.

Use Generic when you run the checkout yourself and want EonAuth to make the keys. Call the URL from your server after the payment is confirmed. The secret is a string; use its UTF-8 bytes as the HMAC key.

  • X-EonAuth-Timestamp: the current unix time, 10 digits. Requests more than 5 minutes off are refused.
  • X-EonAuth-Signature: hex HMAC-SHA256 of timestamp + "." + body.
  • Put an order ID in the body so a retry of the same order is the same body, and gets the same keys.
import { createHmac } from "node:crypto";
const url = "https://panel.eonauth.fyi/hooks/store/store_…";
const secret = process.env.EONAUTH_STORE_SECRET;
async function deliverKeys(orderId, quantity) {
const body = JSON.stringify({ order_id: orderId, quantity });
const timestamp = String(Math.floor(Date.now() / 1000));
const signature = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-EonAuth-Timestamp": timestamp,
"X-EonAuth-Signature": signature,
},
body,
});
if (!res.ok) throw new Error(`EonAuth answered ${res.status}: ${await res.text()}`);
return (await res.json()).keys;
}
200 OK
{ "keys": ["K7NVE-RSAD2-1JRJC-D9T2W-MYFS7", "RMPTA-FNZEA-35G3V-2E3H2-DTC67"] }

Sign exactly the bytes you send. Re-serialising the JSON after signing changes the body and breaks the signature.

Status Body Why
400 INVALID_REQUEST Quantity outside the product’s range
401 UNAUTHORIZED, Invalid signature Wrong secret, wrong header, a changed body, or (Generic) a timestamp more than 5 minutes off
404 NOT_FOUND Unknown URL, or the store product or app is disabled
413 INVALID_REQUEST Body over 64 KB
429 RATE_LIMITED More than 30 calls a minute from one IP

Error bodies use the usual {"ok": false, "error": {...}} shape.

Keys from a store product are paid sales at your price for that tier and duration, with the note store: <product name>. The audit log shows them as store:<product name>, and your license.created webhooks fire.