QuotaGuard and Fireblocks Integration Guide
QuotaGuard and Fireblocks Integration Guide
QuotaGuard gives your Fireblocks API client two fixed outbound IP addresses. Add both to an API key’s IP allowlist once, and every request to the Fireblocks API exits from one of those two IPs, no matter how often your host redeploys or changes its egress. Set QUOTAGUARDSTATIC_URL, point your Fireblocks client’s HTTP layer at it, and the calls Fireblocks rejected for coming from an unrecognized IP start succeeding.
This page covers the Fireblocks REST API at https://api.fireblocks.io and the official Fireblocks SDKs. It does not cover the Co-Signer signing path, which stays in your own environment and is never proxied. That separation is the point, and it is covered below.
Why Fireblocks needs a static IP
Fireblocks lets you lock each API key to specific source IP addresses. The restriction is set per API key (per API user), so a key can be configured to accept calls only from the addresses you list. Once you add any address to a key’s allowlist, a request arriving from any other IP is refused at authentication.
That is the right security posture for a custody or treasury workload. It also breaks the moment your client runs on infrastructure with a changing egress IP, which is almost all modern hosting:
- Containers and serverless functions (AWS Fargate, ECS, Cloud Run, Lambda, Azure Container Apps) draw their outbound IP from a shared pool that rotates.
- Autoscaling groups add and remove nodes, each with a different public IP.
- PaaS hosts (Heroku, Render, Railway, Fly.io) do not give your app a fixed outbound IP by default.
The result is the same: a key that worked yesterday starts returning authentication failures today, because the request is now coming from an address that is not on the allowlist. QuotaGuard fixes this by giving you two static IPs that do not change, so the allowlist stays valid across every deploy.
Getting Started
After creating a QuotaGuard account, you are redirected to your dashboard, where you can find your proxy credentials and two static IP addresses.
Choose the right proxy region: Select the QuotaGuard region closest to where your Fireblocks client runs to minimize latency. The region is set at sign-up. Changes after sign-up require contacting support.
Add both IPs to the API key’s allowlist
In the Fireblocks Console, open the API user (API key) your client uses and add both QuotaGuard static IP addresses to its IP allowlist. Enter them as two separate addresses. Add both, not one: QuotaGuard load-balances across the pair, so requests can exit from either address, and a key that lists only one will intermittently reject traffic that happens to leave from the other.
The allowlist is enforced per API key, so if you run separate keys for staging and production, each key needs both IPs listed. The same QuotaGuard pair can serve multiple keys.
Step 1: set the proxy URL
Your QuotaGuard dashboard shows the connection string. Store it as an environment variable. The host is region-specific, so copy the exact host shown in your dashboard.
export QUOTAGUARDSTATIC_URL="http://username:password@<your-quotaguard-proxy-host>:9293"
<your-quotaguard-proxy-host>:9293 is an example. Use the host for your region from the dashboard. Port 9293 is the HTTP/HTTPS proxy port.
Keep the credentials in your platform’s secret store (Fireblocks Secrets is for Fireblocks keys, not this; use your host’s environment or secret manager) and inject the value at runtime. Do not hard-code it into an image.
Step 2: route your Fireblocks API calls through the proxy
The proxy attaches at the HTTP layer. Your Fireblocks credentials, the X-API-Key header, and the RS256-signed JWT in the Authorization header are all unchanged. The only thing that changes is the source IP Fireblocks sees on the request, which becomes one of your two QuotaGuard static IPs.
How you attach the proxy depends on which SDK you use. The four official Fireblocks SDKs do not behave the same way, so this section is explicit per SDK. Get this right and the rest is one environment variable.
Fireblocks Python SDK (legacy, fireblocks-sdk)
The legacy Python SDK uses requests, which honors the HTTPS_PROXY environment variable. Point HTTPS_PROXY at your QuotaGuard URL before the client is created.
import os
os.environ["HTTPS_PROXY"] = os.environ["QUOTAGUARDSTATIC_URL"]
from fireblocks_sdk import FireblocksSDK
with open("fireblocks_secret.key", "r") as f:
private_key = f.read()
fireblocks = FireblocksSDK(private_key, os.environ["FIREBLOCKS_API_KEY"])
# Calls now exit from your QuotaGuard static IP.
vault_accounts = fireblocks.get_vault_accounts_with_page_info()
Fireblocks Python SDK (new, fireblocks)
The newer fireblocks package is generated from the OpenAPI spec and uses urllib3 under the hood. The user-facing ClientConfiguration does not expose a proxy field, and the SDK does not auto-read HTTPS_PROXY from the environment. If you set the variable and run this SDK, the request goes out directly and Fireblocks sees your host’s real IP, not your QuotaGuard IP.
Two ways to handle it:
- Use the legacy
fireblocks-sdkfor the calls that must be allowlisted. It honorsHTTPS_PROXYas shown above. - Route below the application layer so all outbound traffic from the container or machine egresses through QuotaGuard regardless of the library. Options include a transparent forward proxy (such as Squid) in the container, or platform-level egress rules that direct outbound HTTPS through your QuotaGuard proxy. The library never has to know about the proxy because the routing happens beneath it.
Do not expect HTTPS_PROXY alone to work with the new Python SDK. It will not.
Fireblocks JavaScript SDK (legacy, fireblocks-sdk)
The legacy JS SDK accepts a proxy field in its SDKOptions, typed as an axios proxy config. Pass your QuotaGuard host, port, and credentials.
const { FireblocksSDK } = require("fireblocks-sdk");
const fs = require("fs");
const u = new URL(process.env.QUOTAGUARDSTATIC_URL);
const privateKey = fs.readFileSync("fireblocks_secret.key", "utf8");
const fireblocks = new FireblocksSDK(
privateKey,
process.env.FIREBLOCKS_API_KEY,
"https://api.fireblocks.io",
undefined,
{
proxy: {
protocol: "http",
host: u.hostname,
port: Number(u.port),
auth: { username: u.username, password: decodeURIComponent(u.password) },
},
}
);
Fireblocks TypeScript SDK (new, @fireblocks/ts-sdk)
The new TypeScript SDK uses axios, which reads the HTTPS_PROXY environment variable. Set it in the environment before the process starts, then initialize the client normally.
export HTTPS_PROXY="$QUOTAGUARDSTATIC_URL"
import { Fireblocks } from "@fireblocks/ts-sdk";
import * as fs from "fs";
const fireblocks = new Fireblocks({
apiKey: process.env.FIREBLOCKS_API_KEY,
secretKey: fs.readFileSync("fireblocks_secret.key", "utf8"),
basePath: "https://api.fireblocks.io",
});
// Requests pick up HTTPS_PROXY and exit from your QuotaGuard static IP.
Raw HTTP clients (custom signing, no SDK)
If you sign the JWT yourself and call the API directly, attach the proxy at the HTTP client. The JWT and headers are unchanged; only the egress path moves.
Python, requests:
import os, requests
proxies = {"https": os.environ["QUOTAGUARDSTATIC_URL"]}
r = requests.get("https://api.fireblocks.io/v1/vault/accounts",
headers={"X-API-Key": api_key, "Authorization": f"Bearer {jwt}"},
proxies=proxies)
Node, axios:
const axios = require("axios");
const u = new URL(process.env.QUOTAGUARDSTATIC_URL);
await axios.get("https://api.fireblocks.io/v1/vault/accounts", {
headers: { "X-API-Key": apiKey, Authorization: `Bearer ${jwt}` },
proxy: {
protocol: "http",
host: u.hostname,
port: Number(u.port),
auth: { username: u.username, password: decodeURIComponent(u.password) },
},
});
Go, net/http:
proxyURL, _ := url.Parse(os.Getenv("QUOTAGUARDSTATIC_URL"))
client := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
req, _ := http.NewRequest("GET", "https://api.fireblocks.io/v1/vault/accounts", nil)
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Authorization", "Bearer "+jwt)
resp, _ := client.Do(req)
Ruby, Net::HTTP:
require "net/http"
require "uri"
p = URI(ENV["QUOTAGUARDSTATIC_URL"])
api = URI("https://api.fireblocks.io/v1/vault/accounts")
http = Net::HTTP.new(api.host, api.port, p.host, p.port, p.user, p.password)
http.use_ssl = true
req = Net::HTTP::Get.new(api)
req["X-API-Key"] = api_key
req["Authorization"] = "Bearer #{jwt}"
res = http.request(req)
Java, system proxy properties:
Since Java 8u111, Basic authentication for HTTPS tunneling through an HTTP proxy is disabled by default. You must re-enable it with -Djdk.http.auth.tunneling.disabledSchemes="" for the credentials flags to take effect.
java -Dhttps.proxyHost=<your-quotaguard-proxy-host> \
-Dhttps.proxyPort=9293 \
-Dhttps.proxyUser=username \
-Dhttps.proxyPassword=password \
-Djdk.http.auth.tunneling.disabledSchemes="" \
-jar your-app.jar
For a more flexible approach, use java.net.Authenticator instead of the credentials JVM flags. See our Java HTTPS proxy support article for details.
.NET, WebProxy:
var u = new Uri(Environment.GetEnvironmentVariable("QUOTAGUARDSTATIC_URL"));
var handler = new HttpClientHandler {
Proxy = new WebProxy(u.Host, u.Port) {
Credentials = new NetworkCredential(u.UserInfo.Split(':')[0], u.UserInfo.Split(':')[1])
},
UseProxy = true
};
var client = new HttpClient(handler);
QuotaGuard sits in front of your API calls, not your signing path
QuotaGuard carries the outbound REST traffic from your client to api.fireblocks.io. It does not touch the API Co-Signer or its Callback Handler.
The Co-Signer holds your vault’s key shares and runs in your own production environment, in the cloud or on premise. When a transaction needs signing, the Co-Signer exchanges signing requests and responses with your Callback Handler, an HTTPS server you operate, over a path you control. Putting QuotaGuard in front of your outbound API calls changes only the source IP Fireblocks sees when your client calls the REST API. It inserts nothing into the signing path, and your transaction-authorization and co-signing model is unchanged.
This matters for a custody review: the static-IP layer is scoped to API authentication, not to key custody or transaction approval.
Inbound: webhooks and your receiver
Fireblocks sends webhook events (transaction created, transaction status updated, vault account added, and others) to a URL you register, signed so you can verify each event with the Fireblocks public key. If you need your webhook receiver to present a fixed inbound IP for your own firewall or a fronting partner, QuotaGuard provides a static inbound IP for that receiver.
Keep the Co-Signer Callback Handler out of this. It is part of the signing path and belongs in your own environment, reachable directly by the Co-Signer, not fronted by a proxy.
Testing
Confirm the egress IP before you debug anything else. Call the QuotaGuard IP check through the proxy:
curl -x "$QUOTAGUARDSTATIC_URL" https://ip.quotaguard.com
{"ip":"<one of your two QuotaGuard static IPs>"}
The returned IP must be one of the two static IPs in your dashboard. Run it a few times: because the pair is load-balanced, you should see both addresses appear. Both must be on the API key’s allowlist.
Then confirm a real Fireblocks call succeeds through the proxy. A read call is enough:
# Through your client/SDK configured as above:
# GET https://api.fireblocks.io/v1/vault/accounts -> 200, not 401
A 200 means the request reached Fireblocks from an allowlisted IP. A 401 means it did not. Disambiguate with the IP check above.
Is there a native way to do this?
Fireblocks does not issue egress IPs. It is the destination, so the “native” option is whatever your host platform offers: a NAT gateway with a reserved address, or a single fixed-IP instance. If your platform can give you one stable egress IP and you only run there, that also satisfies the allowlist.
QuotaGuard is the better fit when your host cannot give you a fixed egress IP, when you run across more than one platform or region and want a single identity on the allowlist, when you want the same IP pair to serve several API keys, or when you need the load-balanced pair with failover so a single node issue does not drop your connection to Fireblocks.
Troubleshooting
401, message like “Unauthorized: Token was not accepted,” from a non-allowlisted IP. Fireblocks rejected the request at authentication. The most common proxy-related cause is that the request did not actually route through QuotaGuard, so Fireblocks saw your host’s real IP. This same 401 can also mean a bad or expired JWT, so check the IP path first: run the ip.quotaguard.com test and confirm both returned addresses are on the key’s allowlist. If the IP check is correct and you still get 401, look at the JWT (next item).
New Python SDK (fireblocks) sending requests directly. If you set HTTPS_PROXY and still get rejected, and the IP check shows your host IP rather than a QuotaGuard IP, the new Python SDK is ignoring the proxy because its user-facing configuration does not expose a proxy hook. Switch the allowlisted calls to the legacy fireblocks-sdk, or route at the host level (Squid sidecar, platform egress rules) so the SDK does not need to know about the proxy.
407 Proxy Authentication Required. The proxy credentials in QUOTAGUARDSTATIC_URL are missing or wrong. Re-copy the full string from your dashboard, and if you split it into host, port, and auth for an SDK config, URL-decode the password.
Connection timeout to the proxy. The proxy host or port is wrong for your region, or your platform’s egress firewall is blocking port 9293. Confirm the host matches your dashboard region and that outbound 9293 is allowed.
ip.quotaguard.com returns your host IP, not a QuotaGuard IP. The client is not using the proxy. The environment variable is not being read at runtime, or the library does not honor HTTPS_PROXY (the new Python SDK does not). Set the proxy explicitly in the client config.
JWT exp errors. Fireblocks requires the JWT expiry to be less than 30 seconds after issuance. If your host clock has drifted, requests fail at auth independent of the IP. Sync the clock. This is not a proxy issue, but it produces an auth failure that looks similar.
QuotaGuard Static vs QuotaGuard Shield
| Feature | QuotaGuard Static | QuotaGuard Shield |
|---|---|---|
| Protocol | HTTP / HTTPS / SOCKS5 | HTTPS / SOCKS5 over TLS |
| Customer-to-proxy hop | Plaintext | TLS-encrypted |
| HTTPS payload | Tunneled end-to-end, never decrypted at the proxy | Tunneled end-to-end, never decrypted at the proxy |
| Best for | Most apps | Regulated data or environments that require TLS on every hop |
| Starting price | $19/month | $29/month |
For most Fireblocks API clients, Static is the right choice: outbound HTTPS to api.fireblocks.io is tunneled with HTTP CONNECT and never decrypted at the proxy. Choose Shield if your environment requires TLS on the customer-to-proxy hop as well, which is the common posture for institutional custody and regulated desks. Shield uses SSL passthrough, so QuotaGuard relays the encrypted Fireblocks traffic without being able to read it.
Ready to Get Started?
Get in touch or create a free trial account.