Skip to main content
Every request to a protected Finrock endpoint must carry two headers: a short-lived JWT signed with your RSA-4096 private key, and a static API key issued from the control panel. Together these headers allow the platform to verify both your identity and the exact request you are making — including its body — so that replayed or tampered requests are rejected automatically.

Required Headers

Include both of the following headers on every authenticated API call:
Your API key is generated inside the Finrock control panel under API Users. Each API user has its own key and its own associated RSA public key that you upload during setup.

JWT Payload Fields

Build the JWT payload using the following fields before signing. Every field is required on every request.
string
required
The URI path of the request being made, without the domain. For example: /dac/v1/transactions or /dac/v1/wallets.
string
required
A unique value for this specific request. Use a UUID v4 or a cryptographically random string. Never reuse a nonce — the platform rejects requests with a previously seen nonce.
integer
required
The time at which the JWT was issued, expressed as seconds since the Unix Epoch (e.g. 1715933300). Use your system clock and ensure it is synchronised with NTP.
integer
required
The expiry time of the JWT, expressed as seconds since the Unix Epoch. This value must be less than iat + 30. Tokens with a longer window are rejected with 401.
string
required
Your API key — the same UUID value you pass in the x-api-key header. For example: 4466c45a-7b28-4c50-a0f7-198f8d7f34c5.
string
required
The hex-encoded SHA-256 hash of the raw HTTP request body. For GET requests with no body, hash an empty string. For POST requests, hash the raw JSON string exactly as it will be sent — before any encoding or transformation.
The exp field must be strictly less than iat + 30 seconds. Any JWT with an expiry window greater than 30 seconds is rejected immediately with a 401 Unauthorized response, regardless of signature validity.

Computing the bodyHash

The bodyHash is the lowercase hex-encoded SHA-256 digest of the raw request body string. For requests with no body (such as GET requests), compute the hash of an empty string "". Example — POST request body:
Resulting SHA-256 bodyHash:
Hash the body string before you serialise or compress it further. The hash must match the exact bytes the server receives. Even a single extra space or different key order will produce a different hash and cause a 401.

Generating an RSA-4096 Key Pair

You need an RSA-4096 key pair to sign your JWTs. Generate one locally with OpenSSL, then upload the public key to the Finrock control panel when creating your API user. Keep the private key secret and never share it.
This produces two files:
  • private_key.pem — used by your application to sign JWTs. Never expose this file.
  • public_key.pem — uploaded to the Finrock control panel so the platform can verify your signatures.

Signing the JWT — Code Examples

The examples below show how to build and sign the JWT payload in several languages using the RS256 algorithm. Full, runnable snippets are available at github.com/gofinrock/jwt-snippets.

End-to-End Request Example

Once you have your signed JWT, attach both required headers to every API call.

Common Authentication Errors

The JWT was rejected because either it has already expired, or the exp value is more than 30 seconds ahead of iat. Regenerate the JWT immediately before each request and keep the exp window at 29 seconds or fewer.
The signature on the JWT could not be verified against the public key registered in the control panel. Confirm that you are signing with the private key that corresponds to the public key you uploaded, and that you are using the RS256 algorithm (not HS256 or RS512).
The hash in the JWT payload does not match the body the server received. Ensure you hash the raw JSON string before passing it to your HTTP client. Avoid pretty-printing or re-ordering keys between hashing and sending.
The nonce value in this JWT has been seen before. Generate a fresh UUID or random string for every request — never cache or reuse a nonce.