> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whitebit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Best Practices

> API key management, IP whitelisting, secret storage, key rotation, and webhook verification for WhiteBIT API integrations.

export const RegionBaseUrl = ({className = "", showBaseUrl = true}) => {
  const [region, setRegionState] = useState(() => {
    if (typeof window !== 'undefined') {
      return localStorage.getItem("api-region-preference") || "com";
    }
    return "com";
  });
  const [mounted, setMounted] = useState(false);
  const observerRef = useRef(null);
  const isSyncingRef = useRef(false);
  const updateAllContentOnPage = targetRegion => {
    try {
      const domainFrom = targetRegion === "eu" ? "whitebit.com" : "whitebit.eu";
      const domainTo = targetRegion === "eu" ? "whitebit.eu" : "whitebit.com";
      const links = document.querySelectorAll('a');
      links.forEach(link => {
        let href = link.getAttribute('href');
        if (href && href.includes(domainFrom)) {
          link.setAttribute('href', href.replace(domainFrom, domainTo));
        }
      });
      const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
        acceptNode: node => {
          if (node.parentElement?.closest('.region-toggle-component')) {
            return NodeFilter.FILTER_REJECT;
          }
          return node.textContent.includes(domainFrom) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
      });
      let currentNode;
      while (currentNode = walker.nextNode()) {
        currentNode.textContent = currentNode.textContent.replace(new RegExp(domainFrom, 'g'), domainTo);
      }
      console.log(`[RegionSync] Global content updated to ${domainTo}`);
    } catch (e) {
      console.error("[RegionSync] Error updating content:", e);
    }
  };
  const updateRegion = (newRegion, source) => {
    if (region === newRegion) return;
    console.log(`[RegionBaseUrl] Updating to "${newRegion}" (Source: ${source})`);
    if (source === 'observer') {
      isSyncingRef.current = true;
      setTimeout(() => isSyncingRef.current = false, 1000);
    }
    setRegionState(newRegion);
    localStorage.setItem("api-region-preference", newRegion);
    updateAllContentOnPage(newRegion);
    if (source === 'user-click') {
      window.dispatchEvent(new CustomEvent("regionChange", {
        detail: newRegion
      }));
      attemptToUpdateNativeDropdown(newRegion, 0);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 1), 500);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 2), 1500);
    }
  };
  const attemptToUpdateNativeDropdown = (targetRegion, attempt) => {
    if (isSyncingRef.current) return;
    try {
      const targetUrl = targetRegion === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
      const targetDesc = targetRegion === "eu" ? "EU Server" : "Production Server";
      const selects = document.querySelectorAll('select');
      for (const select of selects) {
        if (select.innerHTML.includes('whitebit.com') || select.innerHTML.includes('whitebit.eu')) {
          select.value = targetUrl;
          select.dispatchEvent(new Event('change', {
            bubbles: true
          }));
          return;
        }
      }
      const buttons = Array.from(document.querySelectorAll('button, [role="combobox"]'));
      const serverSelector = buttons.find(btn => {
        if (btn.closest('a') || btn.closest('[class*="card"]') || btn.closest('nav')) {
          return false;
        }
        const txt = btn.textContent || "";
        const isServerDropdown = (txt.includes('Production Server') || txt.includes('EU Server') || txt.includes('WhiteBIT Global Server') || txt.includes('WhiteBIT EU Server')) && !txt.includes('Run') && !txt.includes('Send') || btn.getAttribute('role') === 'combobox';
        return isServerDropdown;
      });
      if (serverSelector) {
        const currentText = serverSelector.textContent || "";
        if (currentText.includes(targetDesc)) return;
        serverSelector.click();
        setTimeout(() => {
          const options = document.querySelectorAll('[role="option"], li, button');
          for (const opt of options) {
            const optText = opt.textContent || "";
            if (optText.includes(targetDesc) || optText.includes(targetUrl)) {
              opt.click();
              return;
            }
          }
        }, 100);
      }
    } catch (e) {
      console.error("[Sync] Error:", e);
    }
  };
  useEffect(() => {
    setMounted(true);
    updateAllContentOnPage(region);
    const handleStorageChange = e => {
      if (e.key === "api-region-preference" && e.newValue) {
        updateRegion(e.newValue, 'storage');
      }
    };
    const handleRegionChange = e => {
      if (e.detail !== region) {
        updateRegion(e.detail, 'event');
      }
    };
    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("regionChange", handleRegionChange);
    observerRef.current = new MutationObserver(mutations => {
      if (isSyncingRef.current) return;
      updateAllContentOnPage(region);
      for (const mutation of mutations) {
        if (mutation.type !== 'childList' && mutation.type !== 'characterData') continue;
        const target = mutation.target;
        const el = target.nodeType === Node.TEXT_NODE ? target.parentElement : target;
        if (el && (el.getAttribute('role') === 'option' || el.closest('[role="listbox"]'))) continue;
        const text = target.textContent || "";
        if (text.includes('WhiteBIT EU Server') || text.includes('https://whitebit.eu') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'eu') updateRegion('eu', 'observer');
          }
        } else if (text.includes('WhiteBIT Global Server') || text.includes('https://whitebit.com') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'com') updateRegion('com', 'observer');
          }
        }
      }
    });
    observerRef.current.observe(document.body, {
      childList: true,
      subtree: true,
      characterData: true
    });
    if (typeof window !== 'undefined') {
      const current = localStorage.getItem("api-region-preference");
      if (current) attemptToUpdateNativeDropdown(current, 'init');
    }
    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("regionChange", handleRegionChange);
      if (observerRef.current) observerRef.current.disconnect();
    };
  }, [region]);
  const apiBaseUrl = region === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
  if (!mounted) return null;
  return <div className={`flex items-center gap-2 flex-wrap my-4 region-toggle-component ${className}`}>
            <span className="text-sm text-gray-500 dark:text-gray-400 font-mono">
                Base URL
            </span>
            <span className="text-sm text-gray-400">(</span>
            <div className="inline-flex bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5 border border-gray-200 dark:border-gray-700">
                <button onClick={() => updateRegion("com", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "com" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .com
                </button>
                <button onClick={() => updateRegion("eu", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "eu" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .eu
                </button>
            </div>
            <span className="text-sm text-gray-400">)</span>
            {showBaseUrl && <>
                    <span className="text-sm text-gray-400">:</span>
                    <a href={apiBaseUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-primary dark:text-primary-light hover:underline">
                        {apiBaseUrl}
                    </a>
                </>}
        </div>;
};

<RegionBaseUrl />

Secure API integration requires careful management of credentials, network access, and verification mechanisms. The following practices apply to all WhiteBIT API integrations regardless of the use case.

## API key management

Create separate API keys for each application, service, or environment.

* **One key per application:** Do not share a single API key across multiple applications or services. If one application is compromised, revoke the compromised key without affecting other applications.
* **Minimal permissions:** Assign the minimum permission set required:
  * `Info + Trading` — for applications that only read data and place trades
  * `Info + Trading + Deposit + Withdraw` — only for applications that need to move funds
* **No client-side keys:** Never embed API secrets in client-side code (browser JavaScript, mobile apps). API secrets must remain server-side only.
* **Environment isolation:** Use separate API keys for each application or service (e.g., one for the trading bot, one for the monitoring dashboard, one for the withdrawal service).

<Warning>
  Never expose API secrets in client-side code, version control, or log files. Store secrets in environment variables or a dedicated secrets manager.
</Warning>

## IP whitelisting

Restrict API key usage to known IP addresses using the IP whitelist.

* Each API key supports up to **50 IP addresses** in the whitelist
* Configure the whitelist in the API key settings at [whitebit.com/settings/api](https://whitebit.com/settings/api)
* **Production recommendation:** Always enable IP whitelisting for production API keys
* If the application runs on dynamic infrastructure (e.g., autoscaling), whitelist the NAT gateway or load balancer IP range
* IP whitelisting is the primary defense against stolen API credentials

## Secret storage

Store API secrets using environment variables or a dedicated secrets management service.

* **Environment variables:** Store the API secret in an environment variable (e.g., `WHITEBIT_API_SECRET`); load at runtime
* **Secrets managers:** For production deployments, use a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, Azure Key Vault)
* **Never commit secrets:** Add API key files and `.env` files to `.gitignore`
* **No hardcoded secrets:** Secrets must not appear in source code, configuration files committed to version control, Docker images, or CI/CD logs

## Key rotation

Rotate API keys regularly and monitor for inactive key auto-deactivation.

* **Auto-deactivation:** API keys that remain unused for **14 consecutive days** are automatically deactivated
* **Rotation strategy:**
  1. Create a new API key with the same permissions and IP whitelist
  2. Update the application configuration to use the new key
  3. Verify the application works with the new key
  4. Deactivate (delete) the old key
* **Zero-downtime rotation:** If the application supports multiple keys, configure the new key alongside the old key before deactivating the old one
* Monitor key activity to detect unauthorized usage patterns

## Two-factor authentication

Two-factor authentication (2FA) is required for API key creation and management.

* 2FA must be enabled on the WhiteBIT account before creating API keys
* 2FA protects against unauthorized key creation even if account credentials are compromised
* Recommendation: use a hardware security key or authenticator app (not SMS)

## Webhook HMAC verification

Verify the HMAC-SHA512 signature on every incoming webhook to confirm the request originated from WhiteBIT.

WhiteBIT signs all webhook payloads using HMAC-SHA512.

**Verification headers:**

| Header            | Content                                     |
| ----------------- | ------------------------------------------- |
| `X-TXC-APIKEY`    | WhiteBIT webhook API key                    |
| `X-TXC-PAYLOAD`   | Base64-encoded body data                    |
| `X-TXC-SIGNATURE` | `hex(HMAC_SHA512(payload, key=api_secret))` |

**Verification algorithm:**

1. Read the raw request body
2. Base64-encode the body — compare with the `X-TXC-PAYLOAD` header
3. Compute `HMAC_SHA512(base64_encoded_body, api_secret)`
4. Convert to hex string — compare with the `X-TXC-SIGNATURE` header
5. If the signature does not match, reject the request (return non-200 status)

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import hashlib
import hmac
import base64
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "YOUR_WEBHOOK_SECRET"

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    raw_body = request.get_data()

    # Base64-encode the raw body
    encoded_payload = base64.b64encode(raw_body).decode()

    # Verify the payload header matches
    expected_payload = request.headers.get("X-TXC-PAYLOAD", "")
    if encoded_payload != expected_payload:
        return jsonify({"error": "Payload mismatch"}), 400

    # Compute the HMAC-SHA512 signature
    computed_signature = hmac.new(
        WEBHOOK_SECRET.encode(),
        encoded_payload.encode(),
        hashlib.sha512,
    ).hexdigest()

    # Compare with the signature header
    received_signature = request.headers.get("X-TXC-SIGNATURE", "")
    if not hmac.compare_digest(computed_signature, received_signature):
        return jsonify({"error": "Invalid signature"}), 401

    # Process the verified webhook
    data = request.get_json()
    print(f"Verified webhook: {data.get('method')} — ID: {data.get('id')}")
    return jsonify({"status": "ok"}), 200
```

See [Webhooks](/platform/webhook) for the full webhook setup and event types.

## Environment isolation

Separate API credentials by application function to limit the blast radius of any single compromise.

<Warning>
  WhiteBIT does not offer a public testnet or sandbox environment. All API calls execute against the live exchange. Use separate API keys per application and test with minimum order amounts.
</Warning>

Create distinct API keys for distinct concerns:

| Application            | Permissions                           | Rationale                                     |
| ---------------------- | ------------------------------------- | --------------------------------------------- |
| Trading application    | `Info + Trading`                      | Cannot move funds off the exchange            |
| Fund management        | `Info + Trading + Deposit + Withdraw` | Full access required for deposits/withdrawals |
| Monitoring / analytics | `Info + Trading`                      | Read-only usage with no trade execution       |

Each key has a dedicated IP whitelist — configure the whitelist to match the application's infrastructure. Revoking one key does not affect other applications.
