Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.bloobank.com/llms.txt

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

Sending authenticated requests

After generating your ECDSA key pair and registering the public key with Bloobank, you receive an Access Key. That Access Key, together with the cryptographic signature of the request, must be sent in the headers of every authenticated API call. Each authenticated request must include the following headers:
HeaderRequiredDescription
X-Access-KeyYesCredential identifier provided by Bloobank after registering the public key.
X-Access-TimestampYesUnix timestamp in milliseconds, UTC. Must represent the moment the request is sent.
X-Access-Request-IdYesUnique request identifier. UUID v4 is recommended. Must be regenerated on every attempt, including retries.
X-Access-SignatureYesECDSA signature of the request, Base64-encoded.
The private key must never be sent in headers, the request body, or any other field. It must remain exclusively in your secure infrastructure.

How to build the headers

To authenticate a request, follow the steps below:
1

Access Key

The Access Key identifies the credential used in the request. Send it in the X-Access-Key header.
X-Access-Key: <ACCESS_KEY>
Example: 5kUVpgTHq3N2kBfAZEPXvv2v2JQartRcPtAh27KiwzkG
2

Timestamp

Send the Unix time in milliseconds (UTC) in the X-Access-Timestamp header.
X-Access-Timestamp: <TIMESTAMP_IN_MILLISECONDS>
Example: 1715097600000
The timestamp prevents replay attacks. Requests outside the tolerance window are rejected with TIMESTAMP_SKEW_EXCEEDED. Keep your servers synchronized via NTP.
 
3

Request ID

Each request must have a unique identifier (UUID v4) in the X-Access-Request-Id header.
X-Access-Request-Id: <UUID_V4>
Example: f47ac10b-58cc-4372-a567-0e02b2c3d479
Do not reuse X-Access-Request-Id. On retries, generate a new UUID, recompute the canonical string, and generate a new signature.
 
4

Request body hash

Compute the SHA-256 of the raw request body in lowercase hex.
bodySha256Hex = SHA256(rawBody)
Example body:
{"amount":15000,"currency":"BRL","externalId":"order-123456"}
The signed body must be exactly equal to the one sent. Any change in whitespace, field order, or serialization produces a different hash and causes SIGNATURE_INVALID.
For requests with no body, use the SHA-256 of an empty string:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
 
5

Canonical string

Build the canonical string by joining the fields with ::
{accessKey}:{requestId}:{timestamp}:{METHOD}:{pathname}:{bodySha256Hex}
FieldDescription
accessKeySame value as the X-Access-Key header.
requestIdSame value as the X-Access-Request-Id header.
timestampSame value as the X-Access-Timestamp header.
METHODHTTP method in uppercase: GET, POST, PUT, or DELETE.
pathnameURL path, without the query string.
bodySha256HexSHA-256 of the raw body in lowercase hex.
Example:
5kUVpgTHq3N2kBfAZEPXvv2v2JQartRcPtAh27KiwzkG:f47ac10b-58cc-4372-a567-0e02b2c3d479:1715097600000:POST:/v1/pix-out:9f2c0b8f4f5f2d8f0e8d3a7c6f1a0b9e...
The pathname must not include the query string. Use /v1/pix-in, not /v1/pix-in?startDate=2026-05-01.
 
6

Request signature

Compute the SHA-256 of the UTF-8 bytes of the canonical string and sign it with your ECDSA private key.
canonical    = accessKey + ":" + requestId + ":" + timestamp + ":" +
               METHOD + ":" + pathname + ":" + bodySha256Hex

digest       = SHA256(utf8(canonical))
signatureRaw = ECDSA_sign(privateKey, digest)
signature    = Base64(signatureRaw)
Encode the result as standard Base64 (RFC 4648, no line breaks, no URL-safe alphabet) and send it in the X-Access-Signature header:
X-Access-Signature: <BASE64_SIGNATURE>
Signatures must use low-S normalization. High-S signatures are rejected with SIGNATURE_INVALID.

Full example

After completing all steps, the final request should look something like this:
ACCESS_KEY="5kUVpgTHq3N2kBfAZEPXvv2v2JQartRcPtAh27KiwzkG"
TIMESTAMP="1715097600000"
REQUEST_ID="f47ac10b-58cc-4372-a567-0e02b2c3d479"
SIGNATURE="<BASE64_SIGNATURE>"

curl -X POST "https://api.bloobank.com/v1/pix-out" \
  -H "Content-Type: application/json" \
  -H "X-Access-Key: $ACCESS_KEY" \
  -H "X-Access-Timestamp: $TIMESTAMP" \
  -H "X-Access-Request-Id: $REQUEST_ID" \
  -H "X-Access-Signature: $SIGNATURE" \
  -d '{"amount":15000,"currency":"BRL","externalId":"order-123456"}'

Security best practices

Add *.pem and .env to .gitignore from the start. Use a secret manager in production and direnv / dotenv locally.
Staging and production must use different key pairs. Never reuse a staging key in production.
Generate a new key pair every 6–12 months, or immediately if you suspect a compromise. Send the new public key to the Bloobank integration support team, switch traffic after receiving the new X-Access-Key, and request revocation of the old key.
Store the private key in environment variables or a secrets manager such as AWS Secrets Manager, HashiCorp Vault, or Doppler. Never hard-code it in source.
The X-Access-Timestamp header must be within the server’s tolerance window. Clock drift of a few minutes causes 401 errors. Run chronyd or ntpd on your servers.

Common authentication errors

Every attempt — including retries — must use a new UUID. Reusing the same ID returns REPLAY_DETECTED.
The timestamp must be in milliseconds (13 digits in 2026). 1715097600 (10 digits) is in seconds and will be rejected.
The pathname field must not contain the query string. Use /v1/pix-in, not /v1/pix-in?startDate=2026-05-01.
Always compute the hash and sign the exact bytes that will be transmitted. Re-serializing JSON with different field order or whitespace produces a different hash and causes SIGNATURE_INVALID.
Enable low-S normalization in your library (lowS, canonical, or normalize_s). High-S signatures return SIGNATURE_INVALID.
The signature must be encoded as standard Base64 (RFC 4648), not URL-safe Base64. Check that your library is not using - and _ instead of + and /.