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

# Key Link transaction metadata

When Fireblocks Key Link sends a `KEY_LINK_TX_SIGN_REQUEST` message to your Fireblocks Agent, the request carries transaction details produced by Fireblocks' Policy Service, mainly in the `metadata` field. Fireblocks Agent forwards this message to your customer server (the `/messagesToSign` endpoint you implement) exactly as received, so your server can inspect these details — for example, to confirm the amount and destination match what you expect — before authorizing your HSM or key management system to sign.

<Note>
  **This structure is internal and can change without notice**

  `metadata` is not a stable, versioned part of the customer server API — the OpenAPI spec for `/messagesToSign` types it as a bare `object` with no defined shape, and `userAccessToken` (below) isn't in the spec at all. The fields on this page were captured from a real request and cross-checked against Fireblocks' source; they aren't a committed contract. The same field name can even carry different values at different nesting levels of the same request (called out below where observed) — this is genuinely internal, organically-grown data, not a designed schema.

  If your integration reads these fields, parse them defensively:

  * Ignore fields you don't recognize instead of failing.
  * Don't assume a field that's present today will always be present, or that its type/casing stays the same.
  * Don't hard-fail your signing flow solely because a field is missing, unless you've deliberately decided to require it.
</Note>

## Where this appears

A `KEY_LINK_TX_SIGN_REQUEST` you receive at `/messagesToSign` is a `MessageEnvelope`, where `message.payload` is a stringified JSON object. Parsing it gives you a `MessagePayload`:

```json theme={"system"}
{
  "tenantId": "3827c4de-972e-5641-8a51-0a9f713aa708",
  "txId": "50076a36-4580-4354-b8ea-b8c3e3e85479",
  "keyId": "ae060113-45f3-46bd-9528-aa8a246cf04b",
  "userAccessToken": "<redacted JWT>",
  "algorithm": "EDDSA_ED25519",
  "type": "KEY_LINK_TX_SIGN_REQUEST",
  "signingDeviceKeyId": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
  "timestamp": 1789404629,
  "messagesToSign": [{ "message": "0201030766...", "index": 0 }],
  "metadata": { "...": "see below" }
}
```

`timestamp` (and `version`, not shown above) are already part of the official OpenAPI spec for `MessagePayload` — they're not internal/undocumented, just omitted from earlier examples on this page. `userAccessToken`, by contrast, isn't in the OpenAPI spec at all, and Fireblocks Agent itself never reads it — it's a JWT representing the initiating user's session, relayed to your server untouched.

### `metadata`

A flat object mixing a transaction summary with lower-level signing details:

<ResponseField name="txMetaData" type="string">
  A JSON-encoded string. Parse it to get the [TxMetaData](#txmetadata) object described below.
</ResponseField>

<ResponseField name="txMetaDataSignatures" type="array">
  Signatures over the `txMetaData` string. Each entry:

  <Expandable title="properties">
    <ResponseField name="id" type="string">
      The signer's identifier. `policy_service` for the standard signature; can also be a user ID when `type` is `USER_AUTHORIZER`.
    </ResponseField>

    <ResponseField name="type" type="string">
      `SERVICE` (signed by Policy Service) or `USER_AUTHORIZER` (signed by a designated approving user, e.g. under a 2-tier policy).
    </ResponseField>

    <ResponseField name="signature" type="string">
      A hex-encoded RSA-SHA256 signature of the `txMetaData` string.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="signInfo" type="array">
  The raw payload(s) to be signed, prior to their appearance in `messagesToSign`.

  <Expandable title="properties">
    <ResponseField name="payload" type="string">
      Hex-encoded raw bytes of the message to sign.
    </ResponseField>

    <ResponseField name="path" type="array">
      BIP32-style derivation path segments. Observed empty (`[]`) for assets that don't need one.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="signingCount" type="number">
  The number of signature operations Fireblocks expects back for this request.
</ResponseField>

<ResponseField name="asset" type="string">
  The Fireblocks asset ID, e.g. `SOL_TEST`. A flattened copy of the same value inside `txMetaData` — kept in sync with it.
</ResponseField>

<ResponseField name="transactionType" type="string">
  (Optional) The transaction's operation type, e.g. `TRANSFER`. A flattened copy of `txMetaData.transactionType`.
</ResponseField>

<ResponseField name="dstAddress" type="string">
  The primary destination address. A flattened copy of `txMetaData.dstAddress`.
</ResponseField>

<ResponseField name="dstTag" type="string">
  (Optional) Destination tag/memo, for chains that support one. A flattened copy of `txMetaData.dstTag`.
</ResponseField>

<ResponseField name="amount" type="number">
  The transaction amount.
</ResponseField>

<ResponseField name="amountStr" type="string">
  The transaction amount as a string, for precision.
</ResponseField>

<ResponseField name="amountInt" type="string">
  The amount in the asset's smallest unit (e.g. lamports for SOL), as a string.
</ResponseField>

<ResponseField name="amountUSD" type="number">
  The USD value of the transfer.
</ResponseField>

<ResponseField name="fee" type="number">
  The network fee, in the asset's smallest unit.
</ResponseField>

<ResponseField name="coinbaseFee" type="string">
  The network fee as a decimal string, in base asset units.
</ResponseField>

<ResponseField name="chaincode" type="string">
  Hex-encoded HD wallet chaincode used to derive the signing key for this operation.
</ResponseField>

<ResponseField name="srcId" type="string">
  The source ID. This is a string, but legacy transaction flows can send it as a JSON number instead — parse it as a string either way.
</ResponseField>

<ResponseField name="srcType" type="string">
  The source type, e.g. `VAULT`.
</ResponseField>

<ResponseField name="destId" type="string">
  The destination ID.
</ResponseField>

<ResponseField name="destType" type="string">
  The destination type, e.g. `ONE_TIME`. Note this uses a shorter form than `txMetaData.dstType` below (which was `ONE_TIME_ADDRESS` for the same destination) — they aren't guaranteed to match.
</ResponseField>

### Signature verification

Fireblocks Agent verifies the `policy_service` entry in `txMetaDataSignatures` against Fireblocks' Policy Service certificate before it ever calls your `/messagesToSign` endpoint — if that signature doesn't validate, Fireblocks Agent discards the message and never forwards it to your server. By the time your server receives a request, this check has already passed. Fireblocks Agent's current verification logic only checks the `policy_service` entry; it doesn't verify `USER_AUTHORIZER` entries when present.

Fireblocks Agent obtains the Policy Service certificate through its own paired-device credentials, which aren't exposed to your customer server. If you need to re-verify the signature independently (for example, running a modified Fireblocks Agent or a defense-in-depth check), the algorithm is RSA with SHA-256 over the raw `txMetaData` string, and the signature is hex-encoded.

## `TxMetaData`

The object you get from parsing `txMetaData`. This is Policy Service's own transaction/destination representation — the same underlying data model used in the [API Co-signer callback handler's transaction payload](/reference/approve-transactions), though field values aren't guaranteed to line up between the two (see the callouts below).

### Identifiers

<ResponseField name="txId" type="string">
  The transaction's ID.
</ResponseField>

<ResponseField name="tenantId" type="string">
  The workspace (tenant) ID.
</ResponseField>

<ResponseField name="transactionType" type="string">
  The transaction's operation type, e.g. `TRANSFER`.
</ResponseField>

<ResponseField name="asset" type="string">
  The Fireblocks asset ID.
</ResponseField>

### Initiator

<ResponseField name="userId" type="string">
  The ID of the user or API user who created the transaction.
</ResponseField>

<ResponseField name="initiatorId" type="string">
  Observed identical to `userId`.
</ResponseField>

<ResponseField name="userName" type="string">
  (Optional) Display name of the initiating user.
</ResponseField>

### Source

<ResponseField name="srcType" type="string">
  The source type, e.g. `VAULT`.
</ResponseField>

<ResponseField name="srcSubType" type="string">
  (Optional) The specific source subtype. Observed as an empty string when not applicable.
</ResponseField>

<ResponseField name="srcId" type="string">
  The source ID. Always a string here, unlike `metadata.srcId` (above), which can also appear as a JSON number in legacy flows.
</ResponseField>

<ResponseField name="srcName" type="string">
  (Optional) Display name of the source, e.g. the vault account's name. Can carry human-identifiable information — handle it accordingly.
</ResponseField>

<ResponseField name="srcWalletId" type="string">
  (Optional) Set for end-user-wallet sources. Observed as an empty string otherwise.
</ResponseField>

### Destination

<ResponseField name="HAS_DESTINATIONS" type="boolean">
  Whether this transaction uses the multi-destination [`destinations`](#destinations) array. Note the all-caps wire key — unusual among the otherwise camelCase fields here.
</ResponseField>

<ResponseField name="dstType" type="string">
  The primary destination's type, e.g. `ONE_TIME_ADDRESS`. This value doesn't necessarily match the `dstType` enum documented for the [API Co-signer callback handler](/reference/approve-transactions#transactionrequestcallbackdestination) (`ONE_TIME_ADDRESS` here vs. `ONE_TIME` there, for what's otherwise the same kind of destination).
</ResponseField>

<ResponseField name="dstSubType" type="string">
  (Optional) The specific destination subtype. Observed as an empty string when not applicable.
</ResponseField>

<ResponseField name="dstAddressType" type="string">
  `ONE_TIME` or `WHITELISTED`.
</ResponseField>

<ResponseField name="dstId" type="string">
  (Optional) The destination's ID. Empty for one-time addresses.
</ResponseField>

<ResponseField name="dstName" type="string">
  (Optional) Display name of the destination, e.g. an exchange account or vault account name. Can carry human-identifiable information — handle it accordingly.
</ResponseField>

<ResponseField name="dstAddress" type="string">
  The primary destination's address.
</ResponseField>

<ResponseField name="displayDstAddress" type="string">
  The display form of `dstAddress`.
</ResponseField>

<ResponseField name="dstTag" type="string">
  (Optional) Destination tag/memo, for chains that support one. Not present for this Solana example.
</ResponseField>

<ResponseField name="displayDstTag" type="string">
  (Optional) The display form of `dstTag`.
</ResponseField>

<ResponseField name="destinations" type="array">
  Present for multi-destination transactions. One entry per destination — see [below](#destinations).
</ResponseField>

### Amounts and fees

<ResponseField name="amountNative" type="number">
  The transfer amount.
</ResponseField>

<ResponseField name="amountNativeStr" type="string">
  The transfer amount as a string, for precision.
</ResponseField>

<ResponseField name="amountUSD" type="number">
  The USD value of the transfer.
</ResponseField>

<ResponseField name="amountUsdRate" type="string">
  (Optional) The USD exchange rate used to compute `amountUSD`. Not present in every real payload seen — don't treat it as required.
</ResponseField>

<ResponseField name="feeLevel" type="string">
  The fee level used, e.g. `MEDIUM`.
</ResponseField>

<ResponseField name="feeAsset" type="string">
  The asset the fee is paid in.
</ResponseField>

<ResponseField name="feeUsdRate" type="string">
  (Optional) The USD exchange rate used for the fee asset. Not present in every real payload seen — don't treat it as required.
</ResponseField>

<ResponseField name="isGrossAmount" type="boolean">
  (Optional) Whether the fee is deducted from the requested amount. Not present in every real payload seen — don't treat it as required.
</ResponseField>

<ResponseField name="enableCashOut" type="boolean">
  (Optional) Whether cash-out is enabled for this transfer.
</ResponseField>

### Blockchain details

<ResponseField name="blockchain" type="string">
  The blockchain/network asset ID, e.g. `SOL_TEST`.
</ResponseField>

<ResponseField name="networkProtocol" type="string">
  The network protocol, e.g. `SOL`.
</ResponseField>

<ResponseField name="testNet" type="boolean">
  Whether this is a testnet transaction.
</ResponseField>

<ResponseField name="blockHash" type="string">
  (Optional) Chain-specific. Observed for Solana as the blockhash used to build the transaction.
</ResponseField>

<ResponseField name="nonceAccountAddress" type="string">
  (Optional) Chain-specific. Observed for Solana as the nonce account address.
</ResponseField>

### Policy verdict

<ResponseField name="action" type="string">
  The policy verdict for the primary destination, e.g. `ALLOW`.
</ResponseField>

<ResponseField name="actionInfo" type="object">
  Details about the policy rule that produced the verdict. Same shape as [`actionInfo` in the API Co-signer callback handler payload](/reference/approve-transactions#transactionrequestcallbackdestination) — `ruleType`, `byGlobalPolicy`, `byRule`, `capturedRuleV2`, `capturedRuleNumV2`, `rulesSnapshotId`, etc.
</ResponseField>

<ResponseField name="authorizationGroups" type="object">
  (Optional) Present when two-tier authorization applies. Same shape as [`authorizationGroups` in the API Co-signer callback handler payload](/reference/approve-transactions#transactionrequestcallbackdestination) — the exact shape varies by policy version. Observed as `{"groups": []}` when not applicable.
</ResponseField>

<ResponseField name="policyVerdict" type="string">
  The overall policy verdict for the transaction, e.g. `ALLOW`. Observed identical to `action` for a single-destination transfer.
</ResponseField>

<ResponseField name="signerId" type="string">
  The Fireblocks API/console user ID designated to sign, per the matched policy rule.
</ResponseField>

<ResponseField name="signerIds" type="array">
  The full list of designated signer IDs. Observed as a single-element array matching `signerId` for a `SINGLE` designation.
</ResponseField>

### Other fields

<ResponseField name="multiDestFormat" type="boolean">
  (Optional) Related to `HAS_DESTINATIONS`; whether the multi-destination format is in use.
</ResponseField>

<ResponseField name="extraParameters" type="object">
  (Optional) Internal instrumentation. Observed containing only `sendToCoreStartTime` (a Unix timestamp in milliseconds).
</ResponseField>

<ResponseField name="enrichmentJson" type="object">
  (Optional) Enrichment data. Shares its wire key with [`enrichmentJson` in the API Co-signer callback handler payload](/reference/approve-transactions#transactionrequestcallbackdestination), but the fields actually present vary by context — this is a loosely-typed bag, not a fixed schema. Observed here containing only `templateMessageName` (a string), which isn't part of the schema documented on that page.
</ResponseField>

<ResponseField name="txAdditionalDetails" type="object">
  (Optional) Additional context about how the transfer was initiated. Same wire key as [`txAdditionalDetails` in the API Co-signer callback handler payload](/reference/approve-transactions#transactionrequestcallbackdestination), but again a loosely-typed bag — the fields observed here (`availableBalance`, `isAutoCalculatedGasLimit`, `useBroadcastingService`, `useBroadcastOrchestrator`, `psv2MultiInstanceFlow`) don't match the fields documented on that page.
</ResponseField>

<ResponseField name="transactionConfigurations" type="object">
  (Optional) Observed as an empty object (`{}`) in every sample seen so far.
</ResponseField>

<ResponseField name="requestContext" type="object">
  (Optional) Internal request-tracing context.

  <Expandable title="properties">
    <ResponseField name="requestId" type="string">
      An internal request ID, distinct from `txId`.
    </ResponseField>

    <ResponseField name="ddAttributes" type="string">
      A JSON-encoded string of Datadog APM trace-correlation headers. Purely for Fireblocks' internal observability.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="policyInspectionData" type="object">
  (Optional) Internal policy-engine debugging data.

  <Expandable title="properties">
    <ResponseField name="ruleInspectorEncoded" type="string">
      An opaque, Base64-like encoded blob used by Fireblocks' internal policy debugging tools. Not meant to be decoded by customers.
    </ResponseField>
  </Expandable>
</ResponseField>

## `destinations`

Present when `HAS_DESTINATIONS` is `true`. Each entry:

<ResponseField name="dstType" type="string">
  Same value as the top-level `dstType` for a single-destination transfer.
</ResponseField>

<ResponseField name="dstSubType" type="string">
  (Optional) Observed as an empty string when not applicable.
</ResponseField>

<ResponseField name="dstAddressType" type="string">
  `ONE_TIME` or `WHITELISTED`.
</ResponseField>

<ResponseField name="dstAddress" type="string">
  This destination's address.
</ResponseField>

<ResponseField name="dstId" type="string">
  (Optional) This destination's ID. Empty for one-time addresses.
</ResponseField>

<ResponseField name="displayDstAddress" type="string">
  The display form of `dstAddress`.
</ResponseField>

<ResponseField name="amountNative" type="number">
  The amount sent to this destination.
</ResponseField>

<ResponseField name="amountNativeStr" type="string">
  Same amount, as a string.
</ResponseField>

<ResponseField name="amountUSD" type="number">
  The USD value of the amount sent to this destination.
</ResponseField>

<ResponseField name="action" type="string">
  The policy verdict for this specific destination.
</ResponseField>

<ResponseField name="actionInfo" type="object">
  Same shape as the top-level `actionInfo` above, scoped to this destination.
</ResponseField>

<ResponseField name="txAdditionalDetails" type="object">
  (Optional) Same wire key as the top-level field above, scoped to this destination. Observed as an empty object (`{}`) here.
</ResponseField>

<ResponseField name="enrichmentJson" type="object">
  (Optional) Same wire key as the top-level field above, scoped to this destination.
</ResponseField>

## Example

A sanitized version of a real captured `metadata` field (JWT, signatures, and the raw signing payload truncated/redacted; everything else structurally faithful):

```json theme={"system"}
{
  "signInfo": [{ "payload": "0201030766...", "path": [] }],
  "txMetaData": "{\"transactionType\":\"TRANSFER\",\"asset\":\"SOL_TEST\",\"userName\":\"Jane Doe\",\"srcType\":\"VAULT\",\"srcSubType\":\"\",\"srcId\":\"17\",\"srcName\":\"Test Vault Account\",\"srcWalletId\":\"\",\"txId\":\"50076a36-4580-4354-b8ea-b8c3e3e85479\",\"tenantId\":\"3827c4de-972e-5641-8a51-0a9f713aa708\",\"userId\":\"5d1adaa5-d62f-4628-9066-9f211a94c25b\",\"initiatorId\":\"5d1adaa5-d62f-4628-9066-9f211a94c25b\",\"HAS_DESTINATIONS\":false,\"enrichmentJson\":{\"templateMessageName\":\"TRANSFER_REQUEST\"},\"txAdditionalDetails\":{\"availableBalance\":\"3.899975879\",\"isAutoCalculatedGasLimit\":false,\"useBroadcastingService\":true,\"useBroadcastOrchestrator\":false,\"psv2MultiInstanceFlow\":true},\"dstType\":\"ONE_TIME_ADDRESS\",\"dstSubType\":\"\",\"dstId\":\"\",\"dstAddressType\":\"ONE_TIME\",\"dstAddress\":\"11111111111111111111111111111111111111111\",\"displayDstAddress\":\"11111111111111111111111111111111111111111\",\"amountUsdRate\":\"78.9920978006333\",\"amountNative\":1.0,\"feeLevel\":\"MEDIUM\",\"feeAsset\":\"SOL_TEST\",\"feeUsdRate\":\"78.9920978006333\",\"isGrossAmount\":false,\"enableCashOut\":false,\"blockHash\":\"22222222222222222222222222222222222222222\",\"nonceAccountAddress\":\"33333333333333333333333333333333333333333\",\"networkProtocol\":\"SOL\",\"blockchain\":\"SOL_TEST\",\"testNet\":true,\"transactionConfigurations\":{},\"amountNativeStr\":\"1\",\"amountUSD\":78.9920978,\"multiDestFormat\":false,\"action\":\"ALLOW\",\"actionInfo\":{\"byGlobalPolicy\":false,\"ruleType\":\"TENANT\",\"byRule\":true,\"capturedRuleNumV2\":\"1\",\"rulesSnapshotId\":159191},\"authorizationGroups\":{\"groups\":[]},\"signerId\":\"f6f44913-c8da-4e60-aa81-76ffd02633cc\",\"signerIds\":[\"f6f44913-c8da-4e60-aa81-76ffd02633cc\"],\"policyVerdict\":\"ALLOW\"}",
  "asset": "SOL_TEST",
  "dstAddress": "11111111111111111111111111111111111111111",
  "amountStr": "1.000000000",
  "amount": 1.0,
  "amountInt": "1000000000",
  "fee": 19000,
  "coinbaseFee": "0.000019000",
  "srcId": 17,
  "srcType": "VAULT",
  "destId": "",
  "destType": "ONE_TIME",
  "chaincode": "0000000000000000000000000000000000000000000000000000000000000000",
  "txMetaDataSignatures": [
    { "id": "policy_service", "type": "SERVICE", "signature": "7845f537..." }
  ],
  "signingCount": 1,
  "amountUSD": 78.9920978
}
```
