Skip to main content

Documentation Index

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

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

Copy-paste, set your ACCESS_KEY and PRIVATE_KEY, run. Every example below builds the canonical request string, signs it with ECDSA secp256k1, attaches the four headers, and sends a real request to the BlooBank API.
Replace placeholders with values from your secret store. Never commit the private key — use environment variables or a secret manager.

Complete signer client

The example below signs a POST /wallets/{wallet}/paymentOrders request and returns the created payment order.
// npm install @noble/curves @noble/hashes
import { secp256k1 } from '@noble/curves/secp256k1';
import { sha256 } from '@noble/hashes/sha256';
import { randomUUID } from 'node:crypto';

class BlooBank {
  constructor(accessKey, privateKeyHex) {
    this.baseUrl    = 'https://txengine.bloobank.com/txengine/v1';
    this.accessKey  = accessKey;
    this.privateKey = privateKeyHex;
  }

  sign(method, pathname, rawBody) {
    const timestamp = Date.now().toString();
    const requestId = randomUUID();
    const bodyHex   = Buffer.from(sha256(rawBody)).toString('hex');
    const canonical = `${this.accessKey}:${requestId}:${timestamp}:${method.toUpperCase()}:${pathname}:${bodyHex}`;
    const digest    = sha256(new TextEncoder().encode(canonical));
    const sigBytes  = secp256k1.sign(digest, this.privateKey, { lowS: true }).toCompactRawBytes();
    const signatureB64 = Buffer.from(sigBytes).toString('base64');
    return { timestamp, requestId, signatureB64 };
  }

  async request(method, pathname, body) {
    const rawBody = body ? new TextEncoder().encode(JSON.stringify(body)) : new Uint8Array(0);
    const { timestamp, requestId, signatureB64 } = this.sign(method, pathname, rawBody);

    const res = await fetch(`${this.baseUrl}${pathname}`, {
      method,
      headers: {
        'Content-Type':         'application/json',
        'X-Access-Key':         this.accessKey,
        'X-Access-Timestamp':   timestamp,
        'X-Access-Request-Id':  requestId,
        'X-Access-Signature':   signatureB64,
      },
      body: body ? rawBody : undefined,
    });

    if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
    return res.json();
  }

  createPaymentOrder(wallet, body) {
    return this.request('POST', `/wallets/${wallet}/paymentOrders`, body);
  }
}

// ---- usage ----
const client = new BlooBank(
  process.env.BLOOBANK_ACCESS_KEY,
  process.env.BLOOBANK_PRIVATE_KEY,    // 64-char hex
);

const order = await client.createPaymentOrder('production-main', {
  direction:       'IN',
  network:         'br.gov.bcb.pix',
  idempotencyKey:  'invoice-2026-0184',
  amount:          25000,                  // BRL 250.00 in cents
  currency:        'BRL',
  instrument:      { type: 'PIX_CASH_IN_EMV_DYNAMIC', expiresIn: 86400 },
});

console.log(order);

Handling errors in code

Branch on error.status, never on error.message. See Handling errors for the full pattern.
try {
  const order = await client.createPaymentOrder('production-main', body);
  // ...
} catch (err) {
  const status = err.body?.error?.status;
  switch (status) {
    case 'SIGNATURE_INVALID':
      console.error('Check canonical string and low-S normalization');
      break;
    case 'TIMESTAMP_SKEW_EXCEEDED':
      console.error('Clock drift — sync via NTP');
      break;
    case 'REPLAY_DETECTED':
      console.error('Generate a new X-Access-Request-Id and retry');
      break;
    case 'IDEMPOTENCY_KEY_IN_USE_WITH_DIFFERENT_PARAMS':
      console.error('Idempotency key already used with different body');
      break;
    case 'RESOURCE_EXHAUSTED':
      // exponential backoff and retry — see /get-started/errors/retry-strategy
      break;
    default:
      throw err;
  }
}

What’s next

Troubleshooting

11-step SIGNATURE_INVALID checklist.

Idempotency

Make every retry safe.

API reference

Every endpoint with try-it playground.