Webhooks
Receive real-time HTTP notifications when events occur in your store. Webhooks allow you to integrate FluxStore with external services such as Discord bots, CRMs, analytics platforms, and custom applications.
Overview
When an event occurs in your store (e.g. a payment is completed), FluxStore sends an HTTP POST request to your configured webhook URL with a JSON payload describing the event. Your endpoint should respond with a 2xx status code to acknowledge receipt.
Webhooks are a Pro plan feature.
Setup
- Navigate to Dashboard > Webhooks
- Enter your webhook URL
- Toggle the events you want to receive
- Optionally exclude specific packages from triggering webhooks
- Save your configuration
A signing secret is automatically generated when you create your webhook endpoint. Use this secret to verify that incoming requests are genuinely from FluxStore.
Event Types
Payment Events
| Event | Description |
|---|---|
order.completed | A payment has been successfully processed and the order is fulfilled |
order.created | A new order has been placed (before payment is confirmed) |
payment.declined | A payment attempt has been rejected by the payment provider |
payment.refunded | A payment has been refunded (via dashboard or payment provider) |
Dispute Events
| Event | Description |
|---|---|
dispute.opened | A chargeback or payment dispute has been initiated |
dispute.won | A dispute has been resolved in your favor |
dispute.lost | A dispute has been resolved against you |
dispute.closed | A dispute has been closed |
Subscription Events
| Event | Description |
|---|---|
subscription.started | A new subscription has been created |
subscription.renewed | A recurring subscription payment has been processed |
subscription.cancelled | A subscription cancellation has been requested |
subscription.ended | A subscription has been fully terminated |
Utility Events
| Event | Description |
|---|---|
test.ping | Sent when you click “Test Webhook” in the dashboard |
Webhook Payload
All webhooks are sent as POST requests with a Content-Type: application/json header. Every payload follows the same envelope structure:
{
"event": "order.completed",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
// Event-specific data
}
}Payment Event Payloads
order.completed
{
"event": "order.completed",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"order_id": "e5f6a7b8-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"total_amount": 9.99,
"currency": "USD",
"items": [
{
"package_id": "c1d2e3f4-...",
"package_name": "VIP Rank",
"quantity": 1,
"price": 9.99
}
],
"transaction_id": "pi_1234567890",
"gateway": "stripe"
}
}order.created
{
"event": "order.created",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"order_id": "e5f6a7b8-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"total_amount": 9.99,
"currency": "USD",
"items": [
{
"package_id": "c1d2e3f4-...",
"package_name": "VIP Rank",
"quantity": 1,
"price": 9.99
}
]
}
}payment.declined
{
"event": "payment.declined",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"order_id": "e5f6a7b8-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"total_amount": 9.99,
"reason": "Your card was declined."
}
}payment.refunded
{
"event": "payment.refunded",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"order_id": "e5f6a7b8-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"total_amount": 9.99
}
}Dispute Event Payloads
dispute.opened / dispute.won / dispute.lost / dispute.closed
{
"event": "dispute.opened",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"order_id": "e5f6a7b8-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"total_amount": 9.99,
"currency": "USD",
"dispute_id": "dp_1234567890",
"dispute_status": "needs_response",
"reason": "fraudulent"
}
}Subscription Event Payloads
subscription.started
{
"event": "subscription.started",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"subscription_id": "f1a2b3c4-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"package_id": "c1d2e3f4-...",
"package_name": "VIP Monthly",
"payment_provider": "stripe"
}
}subscription.renewed
{
"event": "subscription.renewed",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"subscription_id": "f1a2b3c4-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"package_id": "c1d2e3f4-...",
"package_name": "VIP Monthly",
"renewal_count": 3,
"current_period_end": "2026-04-09T12:00:00Z"
}
}subscription.cancelled
{
"event": "subscription.cancelled",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"subscription_id": "f1a2b3c4-...",
"package_id": "c1d2e3f4-...",
"cancel_at_period_end": true
}
}subscription.ended
{
"event": "subscription.ended",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"subscription_id": "f1a2b3c4-...",
"player_username": "Steve",
"player_uuid": "069a79f4-...",
"package_id": "c1d2e3f4-...",
"package_name": "VIP Monthly"
}
}Test Event Payload
test.ping
{
"event": "test.ping",
"timestamp": "2026-03-09T12:00:00Z",
"store_id": "a1b2c3d4-...",
"data": {
"message": "This is a test webhook from FluxStore."
}
}Security
Signature Verification
Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature of the request body, signed with your webhook signing secret.
Important: It is your responsibility to verify the signature on every incoming webhook request. Without verification, any third party could send forged requests to your endpoint. Always validate the signature before processing webhook data.
To verify the signature:
- Read the raw request body as a string
- Compute HMAC-SHA256 of the body using your signing secret
- Compare the computed signature with the
X-Webhook-Signatureheader value
The signature format is: sha256=<hex-encoded-hash>
Example (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(body, signature, secret) {
const computed = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(signature)
);
}Example (Python)
import hmac
import hashlib
def verify_webhook_signature(body: str, signature: str, secret: str) -> bool:
computed = 'sha256=' + hmac.new(
secret.encode('utf-8'),
body.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)Example (PHP)
function verifyWebhookSignature(string $body, string $signature, string $secret): bool {
$computed = 'sha256=' . hash_hmac('sha256', $body, $secret);
return hash_equals($computed, $signature);
}Example (Java)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
public boolean verifyWebhookSignature(String body, String signature, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(body.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
hex.append(String.format("%02x", b));
}
String computed = "sha256=" + hex.toString();
return MessageDigest.isEqual(
computed.getBytes("UTF-8"),
signature.getBytes("UTF-8")
);
} catch (Exception e) {
return false;
}
}Request Headers
Every webhook request includes the following headers:
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Webhook-Signature | HMAC-SHA256 signature of the request body |
X-Webhook-Event | The event type (e.g. order.completed) |
X-Webhook-Id | Unique delivery ID for idempotency |
Source IP Addresses
Webhook requests are delivered through Cloudflare’s global network, meaning requests may originate from any Cloudflare IP address. We do not recommend IP allowlisting as the sole method of verifying webhook authenticity - use signature verification instead.
However, if you want an additional layer of security, you can restrict incoming requests to Cloudflare’s published IP ranges: https://www.cloudflare.com/ips/
Delivery & Retries
- Webhooks are delivered within seconds of the event occurring
- If your endpoint returns a non-2xx status code or times out, FluxStore will retry up to 3 times
- Retries are attempted every 10 seconds
- After 3 failed attempts, the delivery is marked as failed
- All delivery attempts are logged in the Delivery Logs section of the Webhooks dashboard
- Request timeout is 15 seconds
Best Practices
- Always verify signatures - Verify the
X-Webhook-Signatureheader on every request before processing any data. This is the only reliable way to confirm a request is genuinely from FluxStore. - Respond quickly - Return a
200 OKas soon as possible. Process the webhook data asynchronously if needed. - Handle duplicates - Use the
X-Webhook-Idheader for idempotency. The same event may be delivered more than once. - Use HTTPS - Always use an HTTPS endpoint to protect webhook data in transit.
- Don’t rely on IP allowlisting alone - Requests come from Cloudflare’s IP ranges, which are shared across many services. Signature verification is the authoritative method of authentication.
- Log everything - Keep your own logs of received webhooks for debugging and auditing.