How to get workspace_id?

Support request — GET /v1/workspace returns 403 with an empty body

Subject: GET /v1/workspace returns HTTP 403 with an empty body — which API user role is required, and is the workspaceId in a webhook envelope the same identifier?


Workspace / key details

Workspace name
Workspace type Developer Sandbox
API base URL https://sandbox-api.fireblocks.io
API key ID (first 8 chars)
Role assigned to that API user Editor, no sign admin, security advisor
workspaceId seen in our webhook envelopes redacted
Approximate time of a failed call (UTC) <2026-08-14 09:15Z>

What we are building, and why we need the workspace ID

We are integrating Fireblocks for Litecoin deposits and payouts in our payment gateway, and we consume Webhooks v2 (detached JWS, verified against the JWKS for our environment).

Signature verification proves that an event was signed by Fireblocks, but the JWKS is published per environment, not per workspace — so a valid signature does not prove the event came from our workspace rather than from another tenant of the same environment. To close that gap we pin every one of our merchant contracts to the workspaceId of the workspace it belongs to, and refuse any inbound event whose workspaceId does not match the pinned value.

That means the workspace ID must be obtained out of band, as a configuration value. Reading it from an inbound webhook would defeat the control entirely: an attacker sending a correctly signed event from their own workspace would simply teach us their ID.

Where the documentation says to find it

GET /v1/workspace“Returns the workspace ID and name for the authenticated user” Get workspace - Fireblocks Developer Docs

As far as we can tell this is the only documented way to obtain the value. It is not displayed anywhere we could find in the console.

We have since observed that the Webhooks v2 envelope carries a workspaceId field, and we have read a value from it by making a test deposit into our own workspace. That gives us a candidate value, but we do not consider it a substitute for the API:

  • It is inbound data. Sourcing the configuration of a control that exists to validate inbound data from that same inbound data defeats the control — an attacker delivering a correctly signed event from their own workspace during onboarding would simply teach us their ID.
  • It requires a transaction to have happened. We need to configure this for staging and production workspaces where making a test deposit first is not a reasonable prerequisite.

So the API remains what we want to rely on, and the observed value is something we would like confirmed rather than trusted.

What does not work

Calling GET https://sandbox-api.fireblocks.io/v1/workspace returns:

HTTP 403
(empty response body)

There is no JSON error object and no Fireblocks error code, which makes the refusal hard to distinguish from other failure modes.

Points that we believe rule out the obvious causes:

  1. The request is correctly signed. We use the documented scheme — X-API-Key header plus an RS256 JWT with the uri, nonce, iat, exp, sub and bodyHash claims, with exp less than iat + 30s. An earlier attempt with a longer expiry window did return {"message": "The request has expired", "code": -10}; after shortening it, that error is gone and we get the 403 instead.
  2. The path is right. An earlier attempt against /v1/management/workspace returned {"message": "Endpoint not defined in API specification", "code": -15}. /v1/workspace does not return -15, so the endpoint exists.
  3. The same key and the same signing code work elsewhere. With no change other than the URI, the identical procedure succeeds against /v1/supported_assets and /v1/vault/accounts_paged. Only /v1/workspace is refused.

Minimal reproduction

API_KEY="<api key id>"
SECRET_KEY="<path to the private key .pem>"
BASE_URL="https://sandbox-api.fireblocks.io"
URI="/v1/workspace"

b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }

now=$(date +%s)
body_hash=$(printf '' | openssl dgst -sha256 | awk '{print $NF}')
header=$(printf '{"alg":"RS256","typ":"JWT"}' | b64url)
payload=$(printf '{"uri":"%s","nonce":"%s","iat":%d,"exp":%d,"sub":"%s","bodyHash":"%s"}' \
  "$URI" "$(uuidgen)" "$now" "$((now + 10))" "$API_KEY" "$body_hash" | b64url)
signature=$(printf '%s.%s' "$header" "$payload" | openssl dgst -sha256 -sign "$SECRET_KEY" | b64url)

curl -sS -w '\nHTTP %{http_code}\n' "$BASE_URL$URI" \
  -H "X-API-Key: $API_KEY" \
  -H "Authorization: Bearer $header.$payload.$signature"

Swapping URI for /v1/supported_assets in the same script returns HTTP 200 and a valid payload.

Questions

  1. Is the workspaceId in a Webhooks v2 envelope the same identifier that GET /v1/workspace returns as id? This is our most important question. Our check compares the envelope’s workspaceId against a configured value that we intend to read from /v1/workspace. If the two are different identifiers, or drawn from different namespaces, the comparison would never match and we would silently refuse every genuine deposit and payout notification. Could you also confirm that <uuid observed in the webhook> is the correct workspace ID for the workspace named above?

  2. Which API user role is required for GET /v1/workspace? The API reference page for this endpoint carries no Endpoint Permission line, whereas other endpoints do — for example GET /v1/management/audit_logs states “Admin, Non-Signing Admin, Auditor, Security Admin, Security Auditor”, and the workspace status (Beta) endpoint states “This endpoint is available only for API keys with Admin/Non Signing Admin permissions.” If /v1/workspace has a role requirement, could it please be documented on that page?

  3. Is a read-only role sufficient? We would strongly prefer not to issue an Admin or Non-Signing Admin API key for the sole purpose of reading one UUID. Can an API user with the Security Auditor (or Auditor) role call this endpoint? The audit logs endpoint accepts those roles, so we assume the auditor roles are intended for exactly this kind of read.

  4. Is this endpoint served on Developer Sandbox workspaces at all? If it is not, we will read the value from a Testnet workspace instead — but we would like to know rather than guess.

  5. If the endpoint is unavailable to us, what is the supported way to obtain the ID of a workspace we own? Is the value exposed anywhere in the console UI? We are specifically looking for a method that does not involve trusting an inbound webhook.

  6. Should a 403 from this endpoint carry a response body? Every other refusal we have seen from the API returns a JSON object with a message and a code. The empty body here gave us no signal to act on.

Thank you!