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.
Set up
Section titled “Set up”- In the panel, open the app and go to Seller API & stores → Add product (see Seller API & stores).
- Pick the provider, tier, duration, HWID resets, max quantity and optional key mask.
- For Sell.App and Sellix, paste the store’s webhook secret. For Generic, copy the secret the panel generates (Webhook signing secret — shown once).
- 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.
How each provider is checked
Section titled “How each provider is checked”| 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.
Quantity
Section titled “Quantity”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.
Retries are safe
Section titled “Retries are safe”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.
Generic webhook (your own checkout)
Section titled “Generic webhook (your own checkout)”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 oftimestamp + "." + 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;}import hashlib, hmac, json, os, time, requests
URL = "https://panel.eonauth.fyi/hooks/store/store_…"SECRET = os.environ["EONAUTH_STORE_SECRET"].encode()
def deliver_keys(order_id: str, quantity: int) -> list[str]: body = json.dumps({"order_id": order_id, "quantity": quantity}, separators=(",", ":")) timestamp = str(int(time.time())) signature = hmac.new(SECRET, f"{timestamp}.{body}".encode(), hashlib.sha256).hexdigest() res = requests.post( URL, data=body, headers={ "Content-Type": "application/json", "X-EonAuth-Timestamp": timestamp, "X-EonAuth-Signature": signature, }, timeout=15, ) res.raise_for_status() return res.json()["keys"]BODY='{"order_id":"1042","quantity":2}'TS=$(date +%s)SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$EONAUTH_STORE_SECRET" -hex | sed 's/^.* //')curl -X POST "https://panel.eonauth.fyi/hooks/store/store_…" \ -H "Content-Type: application/json" \ -H "X-EonAuth-Timestamp: $TS" \ -H "X-EonAuth-Signature: $SIG" \ -d "$BODY"{ "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.
Errors
Section titled “Errors”| 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.
What gets recorded
Section titled “What gets recorded”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.