Skip to content
Bare BitcoinDevelopers

Search documentation

↑ ↓ Select · Enter Open · Escape Close

barebitcoin.no ↗
Menu
View Markdown

API keys and signing

Create a key in API-key settings. The key starts with bb/public/; the secret starts with bb/apisecret/. Both are credentials. Do not put them in public source code or URLs.

Headers

Read requests normally require only x-bb-api-key. Signed requests require all three headers:

HeaderValue
x-bb-api-keyComplete API key
x-bb-api-nonceUnsigned decimal integer greater than the last accepted nonce for the key
x-bb-api-hmacBase64-encoded HMAC-SHA256 signature

Enable trading permission for creating and cancelling orders. Enable bitcoin withdrawal permission for initiating withdrawals.

Signature calculation

digest = SHA256(UTF8(decimalNonce) || rawRequestBodyBytes)
message = UTF8(uppercaseMethod) || UTF8(uriPath) || digest
signature = Base64(HMAC_SHA256(Base64Decode(completeSecret), message))

Decode the complete secret, including its prefix. The signed path is the decoded URL path and excludes the query string. For a bodyless request, use zero body bytes. Serialize a JSON body once and send precisely those signed bytes.

Nonce validation and retries

Coordinate signed requests per key. A timestamp alone does not prevent collisions between concurrent requests or out-of-order arrival. The server can consume a nonce before an operation fails; the next attempt needs a larger nonce.

After a timeout, inspect orders or withdrawals before retrying. A nonce is not an idempotency key, and repeating a request can create another transaction.

Node.js signing helper

Save the following as bb-request.mjs. Set BB_API_KEY, BB_API_SECRET, and a coordinated BB_NONCE in your environment. It sends requests to production only when explicitly executed. Replace example destination and path identifiers before use.

JavaScript
import { createHash, createHmac } from 'node:crypto';
import { readFile } from 'node:fs/promises';

export function signature(method, path, nonce, body, secret) {
  const digest = createHash('sha256').update(nonce).update(body).digest();
  return createHmac('sha256', Buffer.from(secret, 'base64'))
    .update(method.toUpperCase())
    .update(path)
    .update(digest)
    .digest('base64');
}

// Importing the helper never sends a request.
if (process.argv[1] && import.meta.url === new URL(process.argv[1], 'file:').href) {
  const [method, path, bodyFile] = process.argv.slice(2);
  const { BB_API_KEY: key, BB_API_SECRET: secret, BB_NONCE: nonce } = process.env;
  if (!key || !secret || !nonce || !/^\d+$/.test(nonce) || !method || !path?.startsWith('/v1/')) {
    throw new Error('Set BB_API_KEY, BB_API_SECRET, BB_NONCE; pass METHOD /v1/path [body.json].');
  }
  const body = bodyFile ? await readFile(bodyFile) : Buffer.alloc(0);
  const url = new URL(path, 'https://api.bb.no');
  const headers = {
    'x-bb-api-key': key,
    'x-bb-api-nonce': nonce,
    'x-bb-api-hmac': signature(method, decodeURIComponent(url.pathname), nonce, body, secret),
    ...(bodyFile ? { 'Content-Type': 'application/json' } : {}),
  };
  const response = await fetch(url, {
    method,
    headers,
    body: bodyFile ? body : undefined,
    redirect: 'error',
  });
  const result = await response.text();
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${result}`);
  console.log(result);
}

For a bodyless cancellation:

Shell
node bb-request.mjs DELETE '/v1/orders/REPLACE_ORDER_ID'

For an order, save the request body to body.json, then run:

Shell
node bb-request.mjs POST '/v1/orders' body.json

Signing fixture

For method POST, path /v1/orders, nonce 1733314678, synthetic secret bb/apisecret/ZZavHDgVRyGowg8blKfPDDRlN3+6h0/vOUA, and these exact body bytes:

JSON
{ "type": "ORDER_TYPE_MARKET", "direction": "DIRECTION_BUY", "amount": 100 }

The expected signature is e6pQ5w9AqVhwHRWXuwS7ZwzRd0kH2GYpHSmtP0cTlSU=. This is a test fixture, not a usable account credential.

Go signing helper

This function constructs a signed request without sending it. Pass the body bytes you intend to send, the complete secret, and a coordinated nonce. Execute the returned request with an HTTP client that has a timeout and rejects redirects.

Go
package signing

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"net/http"
	"strings"
)

func SignedRequest(method, path, key, secret, nonce string, body []byte) (*http.Request, error) {
	if !strings.HasPrefix(path, "/v1/") {
		return nil, fmt.Errorf("path must start with /v1/")
	}
	secretBytes, err := base64.StdEncoding.DecodeString(secret)
	if err != nil {
		return nil, fmt.Errorf("decode API secret: %w", err)
	}
	req, err := http.NewRequest(strings.ToUpper(method), "https://api.bb.no"+path, bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	digest := sha256.Sum256(append([]byte(nonce), body...))
	message := append([]byte(req.Method+req.URL.Path), digest[:]...)
	mac := hmac.New(sha256.New, secretBytes)
	if _, err := mac.Write(message); err != nil {
		return nil, fmt.Errorf("sign request: %w", err)
	}
	req.Header.Set("x-bb-api-key", key)
	req.Header.Set("x-bb-api-nonce", nonce)
	req.Header.Set("x-bb-api-hmac", base64.StdEncoding.EncodeToString(mac.Sum(nil)))
	if len(body) > 0 {
		req.Header.Set("Content-Type", "application/json")
	}
	return req, nil
}