Skip to Content
FluxStore is currently invite-only. Some sections of this documentation are still being written and expanded.
DevelopersWebhooks

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

  1. Navigate to Dashboard > Webhooks
  2. Enter your webhook URL
  3. Toggle the events you want to receive
  4. Optionally exclude specific packages from triggering webhooks
  5. 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

EventDescription
order.completedA payment has been successfully processed and the order is fulfilled
order.createdA new order has been placed (before payment is confirmed)
payment.declinedA payment attempt has been rejected by the payment provider
payment.refundedA payment has been refunded (via dashboard or payment provider)

Dispute Events

EventDescription
dispute.openedA chargeback or payment dispute has been initiated
dispute.wonA dispute has been resolved in your favor
dispute.lostA dispute has been resolved against you
dispute.closedA dispute has been closed

Subscription Events

EventDescription
subscription.startedA new subscription has been created
subscription.renewedA recurring subscription payment has been processed
subscription.cancelledA subscription cancellation has been requested
subscription.endedA subscription has been fully terminated

Utility Events

EventDescription
test.pingSent 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:

  1. Read the raw request body as a string
  2. Compute HMAC-SHA256 of the body using your signing secret
  3. Compare the computed signature with the X-Webhook-Signature header 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:

HeaderDescription
Content-TypeAlways application/json
X-Webhook-SignatureHMAC-SHA256 signature of the request body
X-Webhook-EventThe event type (e.g. order.completed)
X-Webhook-IdUnique 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

  1. Always verify signatures - Verify the X-Webhook-Signature header on every request before processing any data. This is the only reliable way to confirm a request is genuinely from FluxStore.
  2. Respond quickly - Return a 200 OK as soon as possible. Process the webhook data asynchronously if needed.
  3. Handle duplicates - Use the X-Webhook-Id header for idempotency. The same event may be delivered more than once.
  4. Use HTTPS - Always use an HTTPS endpoint to protect webhook data in transit.
  5. 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.
  6. Log everything - Keep your own logs of received webhooks for debugging and auditing.