Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.hubra.app/llms.txt

Use this file to discover all available pages before exploring further.

POST /api/v1/broadcast requires a hubra_token on every call. This page explains why it exists and how to use it.

What it is

A HMAC token issued by /stake, /unstake, and /withdraw. The token is computed over the message bytes of the unsigned transaction the endpoint just built.
hubra_token = HMAC(server_secret, message_bytes_of_unsigned_tx)
The token is opaque to the agent. Treat it as a string. Pass it back to /broadcast alongside the signed transaction.

Why it exists

Without a gate, /broadcast would be a free Solana RPC for arbitrary signed transactions. The server has cost (RPC quota, MEV protection budget, Sanctum execute slots) on every broadcast; allowing arbitrary transactions opens a denial-of-service vector. The HMAC token binds a specific broadcast to a specific Hubra-built transaction:
  • /broadcast recomputes the HMAC over the signed transaction’s message bytes.
  • If it matches, the broadcast proceeds.
  • If it does not, /broadcast returns 403 forbidden.
This keeps the broadcast endpoint useful only for transactions that Hubra actually built, while staying transparent: the agent does not need to authenticate, register, or carry an API key.

Lifetime

Tokens are valid for ~2 minutes after issue. This matches Solana’s blockhash window: a transaction with an expired blockhash cannot be broadcast successfully anyway, so a longer-lived token would not help. If your token expires, rebuild via /stake, /unstake, or /withdraw to get a fresh one. The build is cheap.

Sanctum-specific fields

For Sanctum-routed flows, the broadcast also needs sanctumKind and sanctum_order. These are returned alongside hubra_token in the build response:
{
  "transaction":   "<base64 unsigned tx>",
  "hubra_token":   "<HMAC token>",
  "route":         "sanctum",
  "sanctumKind":   "token",
  "sanctum_order": { "...": "..." },
  ...
}
Forward all four fields (hubra_token, route, sanctumKind, sanctum_order) to /broadcast. Sanctum’s execute endpoint independently validates the signed transaction against the original sanctum_order; without it, Sanctum rejects the broadcast. For non-Sanctum flows, only hubra_token is needed.

What to save

When you call /stake or /unstake, save:
AlwaysSanctum-routed only
Requiredtransaction, hubra_tokensanctumKind, sanctum_order
For sol-native-stake, also save stakeAccount (the new pubkey) — you need it for future deactivate / withdraw / instant unstake.

What you do not need

You do not need to:
  • Decode or parse the token.
  • Refresh it manually (just rebuild if expired).
  • Sign or transform it in any way.
  • Persist it beyond the broadcast.
Treat the token as a single-use, short-lived bearer credential for one specific transaction.

Error: token mismatch

If /broadcast returns 403 forbidden:
{
  "type":   "https://hubra.app/errors/forbidden",
  "title":  "Forbidden",
  "status": 403,
  "detail": "hubra_token does not match this transaction or has expired."
}
Common causes:
  • The token has expired (>2 minutes since issue). Rebuild.
  • The signed transaction’s message bytes do not match the unsigned bytes (you re-serialized differently). Make sure tx.serialize() after signing produces the same message slice as the original.
  • The token belongs to a different transaction (you mixed up two parallel build responses).
In all three cases, the fix is to rebuild the transaction via /stake or /unstake, sign that new transaction, and broadcast with the new token.

See also

POST /broadcast

The endpoint that uses the token.

Errors

403 forbidden and other error slugs.