> ## 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.

# Code examples

> Working ECDSA signer clients in Node.js, Python, Go, Java, PHP, and .NET — copy, set credentials, run.

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.

<Info>
  Replace placeholders with values from your secret store. **Never commit the private key** — use environment variables or a secret manager.
</Info>

## Complete signer client

The example below signs a `POST /wallets/{wallet}/paymentOrders` request and returns the created payment order.

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  // 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);
  ```

  ```python Python theme={"dark"}
  # pip install coincurve requests
  import base64, hashlib, json, os, time, uuid, requests
  from coincurve import PrivateKey

  class BlooBank:
      BASE_URL = 'https://txengine.bloobank.com/txengine/v1'

      def __init__(self, access_key: str, private_key_hex: str):
          self.access_key = access_key
          self.priv       = PrivateKey(bytes.fromhex(private_key_hex))

      def sign(self, method: str, pathname: str, raw_body: bytes):
          timestamp  = str(int(time.time() * 1000))
          request_id = str(uuid.uuid4())
          body_hex   = hashlib.sha256(raw_body).hexdigest()
          canonical  = f'{self.access_key}:{request_id}:{timestamp}:{method.upper()}:{pathname}:{body_hex}'
          digest     = hashlib.sha256(canonical.encode('utf-8')).digest()
          signature  = self.priv.sign(digest, hasher=None)        # DER, accepted
          return timestamp, request_id, base64.b64encode(signature).decode('ascii')

      def request(self, method: str, pathname: str, body=None):
          raw_body = json.dumps(body, separators=(',', ':')).encode() if body else b''
          ts, rid, sig = self.sign(method, pathname, raw_body)
          r = requests.request(
              method, self.BASE_URL + pathname,
              headers={
                  'Content-Type':        'application/json',
                  'X-Access-Key':         self.access_key,
                  'X-Access-Timestamp':   ts,
                  'X-Access-Request-Id':  rid,
                  'X-Access-Signature':   sig,
              },
              data=raw_body if body else None,
          )
          r.raise_for_status()
          return r.json()

      def create_payment_order(self, wallet, body):
          return self.request('POST', f'/wallets/{wallet}/paymentOrders', body)


  client = BlooBank(
      os.environ['BLOOBANK_ACCESS_KEY'],
      os.environ['BLOOBANK_PRIVATE_KEY'],     # 64-char hex
  )

  print(client.create_payment_order('production-main', {
      'direction':      'IN',
      'network':        'br.gov.bcb.pix',
      'idempotencyKey': 'invoice-2026-0184',
      'amount':         25000,
      'currency':       'BRL',
      'instrument':     {'type': 'PIX_CASH_IN_EMV_DYNAMIC', 'expiresIn': 86400},
  }))
  ```

  ```go Go theme={"dark"}
  // go get github.com/btcsuite/btcd/btcec/v2
  // go get github.com/btcsuite/btcd/btcec/v2/ecdsa
  package main

  import (
      "bytes"
      "crypto/sha256"
      "encoding/base64"
      "encoding/hex"
      "encoding/json"
      "fmt"
      "io"
      "net/http"
      "os"
      "strconv"
      "time"

      "github.com/btcsuite/btcd/btcec/v2"
      btcecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa"
      "github.com/google/uuid"
  )

  const baseURL = "https://txengine.bloobank.com/txengine/v1"

  type BlooBank struct {
      AccessKey string
      Priv      *btcec.PrivateKey
  }

  func New(accessKey, privHex string) (*BlooBank, error) {
      raw, err := hex.DecodeString(privHex)
      if err != nil { return nil, err }
      priv, _ := btcec.PrivKeyFromBytes(raw)
      return &BlooBank{AccessKey: accessKey, Priv: priv}, nil
  }

  func (b *BlooBank) sign(method, pathname string, rawBody []byte) (ts, reqID, sig string) {
      ts        = strconv.FormatInt(time.Now().UnixMilli(), 10)
      reqID     = uuid.NewString()
      bodyHex   := fmt.Sprintf("%x", sha256.Sum256(rawBody))
      canonical := fmt.Sprintf("%s:%s:%s:%s:%s:%s", b.AccessKey, reqID, ts, method, pathname, bodyHex)
      digest    := sha256.Sum256([]byte(canonical))
      s         := btcecdsa.Sign(b.Priv, digest[:])
      sig       = base64.StdEncoding.EncodeToString(s.Serialize())
      return
  }

  func (b *BlooBank) request(method, pathname string, body any) (map[string]any, error) {
      var rawBody []byte
      if body != nil { rawBody, _ = json.Marshal(body) }

      ts, reqID, sig := b.sign(method, pathname, rawBody)

      req, _ := http.NewRequest(method, baseURL+pathname, bytes.NewReader(rawBody))
      req.Header.Set("Content-Type",        "application/json")
      req.Header.Set("X-Access-Key",        b.AccessKey)
      req.Header.Set("X-Access-Timestamp",  ts)
      req.Header.Set("X-Access-Request-Id", reqID)
      req.Header.Set("X-Access-Signature",  sig)

      resp, err := http.DefaultClient.Do(req)
      if err != nil { return nil, err }
      defer resp.Body.Close()
      out, _ := io.ReadAll(resp.Body)
      if resp.StatusCode >= 400 { return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, out) }
      var v map[string]any
      _ = json.Unmarshal(out, &v)
      return v, nil
  }

  func main() {
      c, _ := New(os.Getenv("BLOOBANK_ACCESS_KEY"), os.Getenv("BLOOBANK_PRIVATE_KEY"))
      order, err := c.request("POST", "/wallets/production-main/paymentOrders", map[string]any{
          "direction":      "IN",
          "network":        "br.gov.bcb.pix",
          "idempotencyKey": "invoice-2026-0184",
          "amount":         25000,
          "currency":       "BRL",
          "instrument":     map[string]any{"type": "PIX_CASH_IN_EMV_DYNAMIC", "expiresIn": 86400},
      })
      if err != nil { panic(err) }
      fmt.Printf("%+v\n", order)
  }
  ```

  ```java Java theme={"dark"}
  // Maven: org.bouncycastle:bcprov-jdk18on:1.80, com.google.code.gson:gson:2.13.1
  import com.google.gson.Gson;
  import org.bouncycastle.crypto.ec.CustomNamedCurves;
  import org.bouncycastle.crypto.params.*;
  import org.bouncycastle.crypto.signers.ECDSASigner;
  import org.bouncycastle.util.encoders.Hex;
  import java.math.BigInteger;
  import java.net.URI;
  import java.net.http.*;
  import java.security.MessageDigest;
  import java.time.Instant;
  import java.util.*;

  public class BlooBankSigner {
      private static final String BASE_URL    = "https://txengine.bloobank.com/txengine/v1";
      private static final String ACCESS_KEY  = System.getenv("BLOOBANK_ACCESS_KEY");
      private static final String PRIVATE_KEY = System.getenv("BLOOBANK_PRIVATE_KEY");

      public static void main(String[] args) throws Exception {
          var payload = Map.of(
              "direction",      "IN",
              "network",        "br.gov.bcb.pix",
              "idempotencyKey", "invoice-2026-0184",
              "amount",         25000,
              "currency",       "BRL",
              "instrument",     Map.of("type", "PIX_CASH_IN_EMV_DYNAMIC", "expiresIn", 86400)
          );
          var json = new Gson().toJson(payload);
          var pathname = "/wallets/production-main/paymentOrders";

          var timestamp = String.valueOf(Instant.now().toEpochMilli());
          var requestId = UUID.randomUUID().toString();
          var bodyHex   = bytesToHex(sha256(json.getBytes()));
          var canonical = ACCESS_KEY + ":" + requestId + ":" + timestamp + ":POST:" + pathname + ":" + bodyHex;
          var digest    = sha256(canonical.getBytes());

          var curve  = CustomNamedCurves.getByName("secp256k1");
          var domain = new ECDomainParameters(curve.getCurve(), curve.getG(), curve.getN());
          var priv   = new ECPrivateKeyParameters(new BigInteger(1, Hex.decode(PRIVATE_KEY)), domain);

          var signer = new ECDSASigner();
          signer.init(true, priv);
          BigInteger[] rs = signer.generateSignature(digest);
          // Low-S normalization
          BigInteger halfN = curve.getN().shiftRight(1);
          BigInteger s = rs[1].compareTo(halfN) > 0 ? curve.getN().subtract(rs[1]) : rs[1];

          byte[] sig = derEncode(rs[0], s);
          var signatureB64 = Base64.getEncoder().encodeToString(sig);

          var resp = HttpClient.newHttpClient().send(
              HttpRequest.newBuilder()
                  .uri(URI.create(BASE_URL + pathname))
                  .header("Content-Type",        "application/json")
                  .header("X-Access-Key",        ACCESS_KEY)
                  .header("X-Access-Timestamp",  timestamp)
                  .header("X-Access-Request-Id", requestId)
                  .header("X-Access-Signature",  signatureB64)
                  .POST(HttpRequest.BodyPublishers.ofString(json))
                  .build(),
              HttpResponse.BodyHandlers.ofString()
          );
          System.out.println(resp.statusCode() + " " + resp.body());
      }

      static byte[] sha256(byte[] b) throws Exception { return MessageDigest.getInstance("SHA-256").digest(b); }
      static String bytesToHex(byte[] b) { var sb = new StringBuilder(); for (var x : b) sb.append(String.format("%02x", x)); return sb.toString(); }
      static byte[] derEncode(BigInteger r, BigInteger s) {
          byte[] rb = r.toByteArray(), sb = s.toByteArray();
          byte[] out = new byte[6 + rb.length + sb.length];
          out[0] = 0x30; out[1] = (byte)(4 + rb.length + sb.length);
          out[2] = 0x02; out[3] = (byte) rb.length; System.arraycopy(rb, 0, out, 4, rb.length);
          out[4 + rb.length] = 0x02; out[5 + rb.length] = (byte) sb.length;
          System.arraycopy(sb, 0, out, 6 + rb.length, sb.length);
          return out;
      }
  }
  ```

  ```php PHP theme={"dark"}
  <?php
  // Uses OpenSSL.
  class BlooBank {
      const BASE_URL = 'https://txengine.bloobank.com/txengine/v1';
      private string $accessKey;
      private $privateKey;  // OpenSSL resource

      public function __construct(string $accessKey, string $privateKeyPem) {
          $this->accessKey = $accessKey;
          $this->privateKey = openssl_pkey_get_private($privateKeyPem);
          if (!$this->privateKey) throw new Exception('Invalid private key');
      }

      public function createPaymentOrder(string $wallet, array $body): array {
          return $this->request('POST', "/wallets/$wallet/paymentOrders", $body);
      }

      private function sign(string $method, string $pathname, string $rawBody): array {
          $ts        = (string) intval(microtime(true) * 1000);
          $rid       = bin2hex(random_bytes(16));   // UUID-like
          $bodyHex   = hash('sha256', $rawBody);
          $canonical = "$this->accessKey:$rid:$ts:" . strtoupper($method) . ":$pathname:$bodyHex";
          $digest    = hash('sha256', $canonical, true);
          openssl_sign($digest, $signature, $this->privateKey, OPENSSL_ALGO_SHA256);
          return [$ts, $rid, base64_encode($signature)];
      }

      private function request(string $method, string $pathname, ?array $body): array {
          $rawBody = $body ? json_encode($body, JSON_UNESCAPED_SLASHES) : '';
          [$ts, $rid, $sig] = $this->sign($method, $pathname, $rawBody);

          $ch = curl_init(self::BASE_URL . $pathname);
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_CUSTOMREQUEST  => $method,
              CURLOPT_POSTFIELDS     => $rawBody ?: null,
              CURLOPT_HTTPHEADER     => [
                  'Content-Type: application/json',
                  "X-Access-Key: $this->accessKey",
                  "X-Access-Timestamp: $ts",
                  "X-Access-Request-Id: $rid",
                  "X-Access-Signature: $sig",
              ],
          ]);
          $res  = curl_exec($ch);
          $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
          curl_close($ch);
          if ($code >= 400) throw new Exception("HTTP $code: $res");
          return json_decode($res, true);
      }
  }

  $client = new BlooBank(getenv('BLOOBANK_ACCESS_KEY'), file_get_contents('/path/to/privateKey.pem'));
  print_r($client->createPaymentOrder('production-main', [
      'direction'      => 'IN',
      'network'        => 'br.gov.bcb.pix',
      'idempotencyKey' => 'invoice-2026-0184',
      'amount'         => 25000,
      'currency'       => 'BRL',
      'instrument'     => ['type' => 'PIX_CASH_IN_EMV_DYNAMIC', 'expiresIn' => 86400],
  ]));
  ```

  ```csharp .NET theme={"dark"}
  // NuGet: BouncyCastle.Cryptography
  using System.Security.Cryptography;
  using System.Text;
  using System.Text.Json;
  using Org.BouncyCastle.Asn1.Sec;
  using Org.BouncyCastle.Crypto.Parameters;
  using Org.BouncyCastle.Crypto.Signers;
  using Org.BouncyCastle.Math;

  public class BlooBank {
      private const string BaseUrl = "https://txengine.bloobank.com/txengine/v1";
      private readonly string _accessKey;
      private readonly string _privateKeyHex;

      public BlooBank(string accessKey, string privateKeyHex) {
          _accessKey     = accessKey;
          _privateKeyHex = privateKeyHex;
      }

      public async Task<JsonElement> CreatePaymentOrderAsync(string wallet, object body) {
          var json = JsonSerializer.Serialize(body);
          var pathname = $"/wallets/{wallet}/paymentOrders";
          var (ts, rid, sig) = Sign("POST", pathname, Encoding.UTF8.GetBytes(json));

          using var http = new HttpClient();
          var req = new HttpRequestMessage(HttpMethod.Post, BaseUrl + pathname) {
              Content = new StringContent(json, Encoding.UTF8, "application/json"),
          };
          req.Headers.Add("X-Access-Key",        _accessKey);
          req.Headers.Add("X-Access-Timestamp",  ts);
          req.Headers.Add("X-Access-Request-Id", rid);
          req.Headers.Add("X-Access-Signature",  sig);

          var res = await http.SendAsync(req);
          res.EnsureSuccessStatusCode();
          return JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
      }

      private (string ts, string rid, string sig) Sign(string method, string pathname, byte[] rawBody) {
          var ts        = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
          var rid       = Guid.NewGuid().ToString();
          var bodyHex   = Convert.ToHexString(SHA256.HashData(rawBody)).ToLowerInvariant();
          var canonical = $"{_accessKey}:{rid}:{ts}:{method}:{pathname}:{bodyHex}";
          var digest    = SHA256.HashData(Encoding.UTF8.GetBytes(canonical));

          var curve  = SecNamedCurves.GetByName("secp256k1");
          var domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
          var priv   = new ECPrivateKeyParameters(new BigInteger(1, Convert.FromHexString(_privateKeyHex)), domain);

          var signer = new ECDsaSigner();
          signer.Init(true, priv);
          var rs = signer.GenerateSignature(digest);
          // Low-S normalization
          var halfN = curve.N.ShiftRight(1);
          var s     = rs[1].CompareTo(halfN) > 0 ? curve.N.Subtract(rs[1]) : rs[1];

          // DER encode
          var derR = rs[0].ToByteArray(); var derS = s.ToByteArray();
          var der  = new byte[6 + derR.Length + derS.Length];
          der[0] = 0x30; der[1] = (byte)(4 + derR.Length + derS.Length);
          der[2] = 0x02; der[3] = (byte) derR.Length; Array.Copy(derR, 0, der, 4, derR.Length);
          der[4 + derR.Length] = 0x02; der[5 + derR.Length] = (byte) derS.Length;
          Array.Copy(derS, 0, der, 6 + derR.Length, derS.Length);

          return (ts, rid, Convert.ToBase64String(der));
      }
  }
  ```
</CodeGroup>

## Handling errors in code

Branch on `error.status`, never on `error.message`. See [Handling errors](/get-started/errors/handling) for the full pattern.

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  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;
    }
  }
  ```

  ```python Python theme={"dark"}
  from requests.exceptions import HTTPError

  try:
      order = client.create_payment_order('production-main', body)
  except HTTPError as e:
      err = e.response.json().get('error', {})
      status = err.get('status')
      if status == 'SIGNATURE_INVALID':
          print('Check canonical string and low-S normalization')
      elif status == 'TIMESTAMP_SKEW_EXCEEDED':
          print('Clock drift — sync via NTP')
      elif status == 'REPLAY_DETECTED':
          print('Generate a new X-Access-Request-Id and retry')
      elif status == 'IDEMPOTENCY_KEY_IN_USE_WITH_DIFFERENT_PARAMS':
          print('Idempotency key already used with different body')
      else:
          raise
  ```
</CodeGroup>

## What's next

<CardGroup cols={3}>
  <Card title="Troubleshooting" icon="wrench" href="/get-started/authentication/troubleshooting">
    11-step SIGNATURE\_INVALID checklist.
  </Card>

  <Card title="Idempotency" icon="rotate" href="/get-started/concepts/idempotency">
    Make every retry safe.
  </Card>

  <Card title="API reference" icon="book-open" href="/api-reference/introduction">
    Every endpoint with try-it playground.
  </Card>
</CardGroup>
