Skip to main content
Every authenticated request to the BlooBank API carries the same four headers and a signature computed over a deterministic canonical request string. This page is the normative reference — get it right once and every endpoint works. For working code in your language, see Code examples. For diagnostics, see Troubleshooting.

The four required headers

The private key never leaves your environment. It is used to sign the canonical request locally. The only thing that travels over the wire is the resulting Base64 signature.

Build the canonical request string

The signature is computed over a deterministic string both sides build identically:
Fields are joined with the ASCII colon : (0x3A). No whitespace, no trailing newline, encoded as UTF-8.

The three most common mistakes

If your URL is /wallets/main/paymentOrders?status=SUCCESS, the {pathname} field is /wallets/main/paymentOrders — the ?status=SUCCESS part is stripped. Sign the path only.
Hash the exact bytes that will travel on the wire — not a re-serialized version. If you parse JSON into an object and re-stringify it before hashing, whitespace, key order, and escape sequences may differ from what your HTTP client eventually sends. Result: SIGNATURE_INVALID.
Read the current time once, store it in a variable, and use the same value in both the X-Access-Timestamp header and the {timestamp} field of the canonical string. Reading the clock twice — even microseconds apart — gives you a different value in each place.

Sign and encode

1

SHA-256 of the canonical string

Compute the SHA-256 digest of the UTF-8 bytes of the canonical request. This yields a 32-byte digest.
2

ECDSA sign

Sign the digest with your private key on secp256k1.
3

Low-S normalization (mandatory)

The signature must be in low-S form (i.e., s ≤ n/2). Most modern libraries do this by default; some (notably WebCrypto) do not. Enable the lowS / canonical / normalize_s flag. See Troubleshooting.
4

Base64 encode

Standard Base64 (RFC 4648), not URL-safe. No line breaks. Both DER and P1363 (r || s) signature encodings are accepted — whichever your library emits will work.
5

Attach as X-Access-Signature

Send the Base64 result in the X-Access-Signature header alongside the other three.

Pseudocode

Worked example — GET with query, no body

Target request:
Intermediate values: Canonical request:
Sign that string with your private key and Base64 the result. The ?filter=status%3DACTIVE query stays on the URL line but is not included in the canonical request.

Worked example — POST with JSON body

Target request:
Intermediate values: The canonical request follows the same format. If your serializer changes a single byte of the JSON between hashing and sending, you will get SIGNATURE_INVALID — hash the bytes you are about to send, not a re-parsed copy.

Send the request

The final HTTP request:
A successful response is 201 Created with the created payment order in the body.

Security checklist

  • Never hardcode the private key. Load it from a secret manager.
  • Never transmit the private key. The signature alone goes over the wire.
  • Synchronize your clock. With a ±10-second window, NTP is mandatory.
  • Generate a fresh X-Access-Request-Id per attempt. Including retries.
  • Use HTTPS only. TLS encrypts the body; the signature only proves authenticity.
  • Rotate credentials periodically. Every 6–12 months at minimum.
  • Log decisionId and excRecordId on failure — they let support trace your request server-side. See Errors.

Next

Code examples

Working clients in Node.js, Python, Go, Java, PHP, and .NET.

Troubleshooting

Diagnose SIGNATURE_INVALID in 11 steps.

Errors

Branch on error.status, retry safely.