Analyse
IntegrationsCustom webhooks

Integrations

Custom webhooks

Send signed purchase JSON from any custom store or checkout backend into Analyse.

Custom webhooks let any store or checkout system send purchases into Analyse. Use this when you do not use Tebex or CraftingStore, or when your store already has its own backend that can send signed JSON after a payment completes.

Once connected, custom webhook purchases show up in Store analytics and can be attributed back to the player and their original Campaign.

How it works

  1. A player buys something on your store.
  2. Your store or backend sends a JSON POST to the Analyse custom webhook URL for that Server.
  3. Analyse verifies the X-Signature header using the webhook secret.
  4. Analyse records the purchase, matches it to the player by username, and attributes it to the Campaign that originally brought them in.

Setup

  1. 1

    Open the custom webhook integration in Analyse

    Go to Settings → Integrations → Custom Webhook → Connect.

  2. 2

    Generate the webhook secret

    Analyse shows a unique webhook URL and a signing secret. Keep the secret private.

  3. 3

    Add the URL to your store or backend

    Send a POST request to the full URL Analyse gives you. It includes the Server's projectId; do not remove that query parameter.

  4. 4

    Sign each request

    Generate the signature from the exact JSON body you are sending and place it in the X-Signature header.

  5. 5

    Send a test purchase

    After Analyse accepts the first request, the integration's last sync time updates and the purchase appears in Store analytics.

Keep the body exact

The signature is calculated from the raw request body. If you sign one JSON string and send a differently formatted JSON string, the request returns 401.

Webhook URL

The URL looks like this:

text
https://www.analyse.net/api/webhooks/custom?projectId=SERVER_ID

Copy the real URL from Analyse. Each Server has its own projectId, so using the wrong URL sends the purchase to the wrong Server or returns 404.

Payload

Analyse expects JSON with three required fields:

FieldRequiredTypeNotes
transaction_idYesstringUnique ID from your store. Reusing the same value is treated as a duplicate purchase.
usernameYesstringMinecraft username to match against players on the Server.
amountYesnumberTotal purchase amount in the currency's normal unit, for example 9.99.
currencyNostringDefaults to USD when omitted.
itemsNoarrayLine items shown in Store analytics.
emailNostringStored on the purchase when present.
timestampNostringISO timestamp for when the purchase happened. Defaults to the receive time.

Example:

json
{
"transaction_id": "order-123",
"username": "PlayerName",
"amount": 9.99,
"currency": "USD",
"items": [
{
"name": "Diamond Rank",
"quantity": 1,
"price": 9.99
}
],
"email": "player@example.com",
"timestamp": "2026-03-27T12:00:00Z"
}

If items is omitted, Analyse creates one item named Purchase with the purchase amount.

Each item should use this shape:

FieldRequiredTypeNotes
nameYesstringProduct or package name shown in Store analytics.
priceYesnumberPrice for this line item.
quantityNonumberDefaults to 1 when omitted.

Signing requests

Custom webhooks use HMAC-SHA256(SHA256(rawBody), webhookSecret).

That means:

  1. Turn the JSON payload into the exact string you will send.
  2. SHA-256 hash that string and encode the hash as lowercase hex.
  3. HMAC-SHA256 that hex hash with your webhook secret.
  4. Send the HMAC hex digest in the X-Signature header.

Node.js example

javascript
const crypto = require("crypto");
const webhookUrl = "https://www.analyse.net/api/webhooks/custom?projectId=SERVER_ID";
const secret = "whsec_your_secret_here";
const body = JSON.stringify({
transaction_id: "order-123",
username: "PlayerName",
amount: 9.99,
currency: "USD",
items: [{ name: "Diamond Rank", quantity: 1, price: 9.99 }],
});
const bodyHash = crypto.createHash("sha256").update(body, "utf8").digest("hex");
const signature = crypto.createHmac("sha256", secret).update(bodyHash).digest("hex");
await fetch(webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Signature": signature,
},
body,
});

cURL example

bash
WEBHOOK_URL="https://www.analyse.net/api/webhooks/custom?projectId=SERVER_ID"
WEBHOOK_SECRET="whsec_your_secret_here"
BODY='{"transaction_id":"order-123","username":"PlayerName","amount":9.99,"currency":"USD"}'
BODY_HASH=$(printf "%s" "$BODY" | shasum -a 256 | cut -d " " -f1)
SIGNATURE=$(printf "%s" "$BODY_HASH" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | cut -d " " -f2)
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-H "X-Signature: $SIGNATURE" \
-d "$BODY"

Responses

StatusMeaning
200Purchase accepted, or the same transaction_id was already processed.
400Missing projectId, invalid JSON, missing required fields, or invalid amount.
401Signature does not match the body and secret.
404Custom webhook integration is not enabled for that Server.

Amounts must be finite, non-negative numbers. Analyse rejects unusually large values above 1000000 to prevent accidental bad data.

How players get matched

Analyse matches the purchase to a player by username case-insensitively. If that player has joined the Server before, the purchase is linked to their player record and inherits their original Campaign attribution.

If no player matches, the purchase is still stored at the Server level and contributes to Store totals, but it is not attributed to a player or Campaign.

Duplicates and refunds

Custom webhooks currently create completed purchases only. Sending the same transaction_id again is safe; Analyse treats it as already processed and does not double-count revenue.

Refunds and chargebacks are not currently handled by the custom webhook endpoint. If your store needs full refund automation, use the Tebex integration where possible.

Rotating the secret

If the secret leaks, open Settings → Integrations → Custom Webhook and regenerate it. Analyse starts rejecting requests signed with the old secret immediately, so update your store or backend before sending more purchases.