_Docs/
Get StartedModulesPlatformDeployCookbookChangelogReference
_Stack
_Modules
  • Ledger
  • Numscript
  • Connectivity
    • Capabilities
    • Operations
    • Accounts
    • Payments
    • Transfer Initiation
    • Account Pools
    • Payment Service Users
    • Connectors
      • Generic ConnectorEE
        • Getting Started
        • How it Works
      • PSP Connectors
        • Adyen
        • Atlar
        • Bankingcircle
        • Column
        • Currencycloud
        • Increase
        • Mangopay
        • Modulr
        • Moneycorp
        • Qonto
        • Stripe
        • Wise
        • Banking BridgeEE
        • RoutableEE
      • Exchange Connectors
        • Coinbase PrimeEE
          • Payments
          • Conversions
          • Orders
        • FireblocksEE
        • BitstampEE
      • Open BankingEE
        • Getting Started with Open Banking
        • Plaid
        • Tink
        • Powens
      • Build a connector
  • WalletsEE
  • FlowsEE
  • ReconciliationEE
  1. Modules
  2. Connectivity
  3. Connectors
  4. Exchange Connectors
  5. Bitstamp
Bitstamp
Exchange Connectors

Bitstamp

Connect a Bitstamp account to Formance Connectivity to sync wallets, balances, payments, orders, and conversions.

Requires payments 3.3.0+.

The Bitstamp connector polls a Bitstamp account and surfaces its currency wallets, balances, payments, trading orders, and conversions to the Connectivity service. It is read-only and spot-only.

Bitstamp API keys are scoped to a single account — Main, or one named sub-account — so this connector follows a one-install-per-scope model. The X-Auth-Subaccount-Id header was confirmed non-functional through live probing; reconciling activity across multiple sub-accounts means installing one connector per scope and joining results downstream.

Prerequisites#

You need a Bitstamp account and an API key dedicated to Formance, with the least permissions required for the capabilities you plan to use. Bitstamp uses HMAC-SHA256 v2 signing — every request carries five headers (X-Auth, X-Auth-Signature, X-Auth-Nonce, X-Auth-Timestamp, X-Auth-Version: v2). The connector handles the signing internally; you only supply the key and secret.

Make sure to create an API key dedicated to Formance. Doing so will improve your auditability and security and will allow you to revoke access to Formance at any time if needed.

Installation#

curl -X POST $FORMANCE_API_URL/api/payments/connectors/bitstamp \
  -H "Content-Type: application/json" \
  -d @config.json
POST/api/payments/connectors/bitstamp

With config.json containing:

JSON
{
  "apiKey": "string",
  "apiSecret": "string",
  "endpoint": "https://www.bitstamp.net",
  "name": "string",
  "pollingPeriod": "30m"
}

Configuration fields#

FieldRequiredDefaultDescription
apiKeyyes—Bitstamp API key. Sent in the X-Auth header as BITSTAMP <apiKey>.
apiSecretyes—HMAC-SHA256 signing secret. Never logged.
endpointnohttps://www.bitstamp.netAPI root. Override only for non-production environments.
nameyes—A unique name for this connector instance. Useful when running one connector per Bitstamp account scope (e.g. bitstamp-main, bitstamp-treasury).
pollingPeriodno30mSync cadence (minimum 20m). Drives every capability — Accounts, Balances, Payments, Orders, Conversions.

The config is deliberately minimal. There is no accountScope or subAccountId flag — the API key already scopes the connection, and Bitstamp's API exposes no portable way to fan out across scopes. Adding a scope flag would let a misconfigured install claim to be Main while authenticating as a sub-account.

Capabilities#

The Bitstamp connector exposes the following capabilities:

  • FetchAccounts — currency wallets in the account scope, via POST /api/v2/account_balances/.
  • FetchBalances — derived from the PSPAccount.Raw snapshot already returned by Accounts; no second API call.
  • FetchPayments — three-source union over user_transactions/, crypto-transactions/ (Main-only), and withdrawal-requests/.
  • FetchOrders — open-orders snapshot reconciled against order_status/ per tracked id.
  • FetchConversions — user_transactions/ rows with type=36 (instant buy/sell, atomic two-asset swaps).

Payouts, transfers, webhooks, and bank-account creation are not implemented today — Bitstamp's API surface for those flows is uneven and not yet covered.

Workflow tree#

Four periodic root tasks, each with its own cursor, advance independently:

FetchAccounts (periodic)
  └── FetchBalances (FromPayload — no extra API call)

FetchPayments     (periodic root)
FetchOrders       (periodic root)
FetchConversions  (periodic root)

Bitstamp's payments / orders / conversions endpoints are account-global at the API-key level, so no parent-account context is needed for those roots. FetchBalances runs nested under FetchAccounts because the balance values are already present in the account_balances/ response — calling a second endpoint would be wasted work.

Account model#

Every internal account in Connectivity corresponds to a single currency in the Bitstamp account scope — one PSPAccount per (connector install, currency). The reference is the currency ticker (USD, EUR, BTC); the connector-level name (e.g. bitstamp-main) disambiguates the scope, so the per-currency name only needs to carry the ticker.

Bitstamp returns every currency the account could hold, even with all values at zero. Rows where Available, Total, and Reserved are all zero are skipped at the orchestrator — emitting hundreds of empty accounts pollutes the catalogue without informing anyone.

Bitstamp does not expose per-currency creation dates, so CreatedAt defaults to BitstampGenesis = 2011-08-02 UTC (the platform's launch date). The sentinel is stable across reinstalls, which keeps the field meaningful even though it isn't truthful.

Asset model#

The canonical asset string is the currency ticker, uppercased, with the precision suffix from Bitstamp's currencies cache — USD/2, EUR/2, BTC/8, USDT/6. The connector loads the cache at install time and refreshes it on a TTL; assets not in the cache are logged and skipped rather than emitted with a guessed precision.

Payments — three-source union#

A Formance Payment originates from one of three Bitstamp endpoints, fanned in by the FetchPayments orchestrator. Each source has its own ID space and its own cursor; the engine dedupes the union by (source, id). Every payment carries com.bitstamp.spec/source so downstream consumers can tell where a row came from.

SourceScopeCursorWhat it carries
user_transactions/Main + subsince_id watermark on tx.IDSettled fiat / crypto activity, deposits, payouts, sub-account transfer legs (types 14 / 33 / 35). Excludes type=2 trades and type=36 instant buy/sell — those feed Orders and Conversions respectively.
crypto-transactions/Main onlyPer-bucket datetime Unix-seconds (deposits, withdrawals, ripple IOUs each track separately)On-chain crypto flows with explicit network + txid + destination address. Sub-account scopes hit the try-and-skip cache — see Install-time enrichment.
withdrawal-requests/Main + subid-based after a cold-start offset walkFiat withdrawal lifecycle with status enum (PENDING / SUCCEEDED / CANCELLED / FAILED) and an explicit scheme (SEPA, wire, ACH, crypto).

user_transactions/ watermarks are inclusive — the last row of cycle N reappears as the first row of cycle N+1, deduped downstream by PSPPayment.Reference. End-of-pagination keeps the watermark; we never reset.

Sub-account transfers#

When a transfer crosses two scopes inside the same Bitstamp account, the connector emits each leg as a signed payment with a shared correlation key:

LegTypeAmounttransfer_pair_idtransfer_direction
Source sidePAYOUTabs(amount)tx.idoutgoing
Destination sidePAYINamounttx.id (identical)incoming

Why not a single TRANSFER row? Each Bitstamp connector sees only one side of a sub-account transfer (its API key scopes it that way). The Formance PSPPayment model expects a single connector to populate both account references; it can't, so the payout / payin pair is the only model that stays internally consistent. Downstream consumers reconstruct the full transfer by joining (transfer_pair_id, asset).

Live-probed reality: a Main-account API key does NOT actually surface type-14 / 33 / 35 rows in user_transactions/ today, even when the web UI shows transfers. The mapping above stays as defensive code that activates the moment Bitstamp exposes the rows on any polled endpoint. Customers who need transfer reconciliation install one connector per sub-account; the pair-id correlation works once both legs' API keys are integrated.

Orders#

Bitstamp does not expose an "orders since X" endpoint. The connector reconciles a live snapshot every cycle:

  1. GetOpenOrders returns the currently-open orders.
  2. New IDs are seeded into the trackedOrders state with their first-sight LimitPrice.
  3. GetOrderStatus is called per id (snapshot ∪ tracked) for the rich shape — fills, fees, datetime, market.
  4. The order is mapped to a PSPOrder, with the adjustments list aggregating each observed state change.
  5. Tracked entries drop on terminal status (FILLED / CANCELLED).
  6. Tracked entries also drop after FirstSeenAt + 25d, with com.bitstamp.spec/retention_expired = true on the final emission. Bitstamp retains order_status/ rows for 30 days; the 5-day safety margin avoids losing an order's terminal state to retention.

The Trade primitive that Bitstamp surfaces in user_transactions/ (type=2 rows carrying a parent order_id) is aggregated under its parent order rather than emitted as a standalone Order. One PSPOrder per Bitstamp order, fills attached.

Conversions#

Bitstamp returns two primitives in user_transactions/ that both look like "buys" and "sells" in the web UI. The reliable distinction:

WireHas order_id?LifecycleFormance model
type=2 (Trade — order fill)yesorder-book — In Queue → Open → Finished / CancelledPSPOrder
type=36 (Instant buy/sell)noatomic — settled in one round-tripPSPConversion

The conversions task shares the user_transactions/ stream with payments but holds its own watermark, so the two cursors advance independently. Asset class plays no role in the classification — Bitstamp tags every crypto (BTC, USDC, EURC, …) as currency.type = "crypto" with no native stablecoin tag. A type=36 BTC↔EUR row and a type=36 USDC↔EUR row are the same primitive (spot-priced atomic swap); downstream consumers wanting "market exposure" vs "stable-value swap" semantics apply their own stablecoin allow-list against SourceAsset / DestinationAsset.

Install-time enrichment#

The connector loads four reference datasets in parallel at install time, refreshed via a TTL cache:

  • markets — every trading pair Bitstamp offers, used to resolve order quote / base currencies.
  • my_markets — pairs the API key has actually traded (gates Order details to permitted markets).
  • fees/trading — per-market trading fees, surfaced on Order metadata.
  • fees/withdrawal — per-currency withdrawal fees, surfaced on withdrawal-request payments.

Permission-gated endpoints feed a process-lifetime derivSkip cache: the first 403-style response for a key without my_markets scope is logged once at Info level, then subsequent attempts on the same key are silenced. This keeps logs readable when an install runs against a read-only key without trading scope.

Metadata keys#

Every Bitstamp-specific field lives under the com.bitstamp.spec/ namespace. The full inventory lives in the connector's MAPPINGS.md; the operationally useful highlights:

  • Account: currency_type, currency_decimals, withdrawal_fee?, is_crypto?.
  • Payment: source (one of user_transactions, crypto_transactions, withdrawal_requests), plus source-specific keys — network, txid, destination_address, bank_transaction_id?, pending_reason?, transfer_pair_id?, transfer_direction?.
  • Order: order_subtype (LIMIT / MARKET / INSTANT / STOP_LIMIT), order_status_datetime, client_order_id?, historical?, retention_expired?.
  • Conversion: from_amount_raw, to_amount_raw, fee_market.

Pagination and recovery#

Each source carries an opaque cursor in connector state. The watermark is immutable for the duration of a cycle — advancing mid-cycle would create a page-2-tighter-than-page-1 race that drops rows whose timestamp lands between page boundaries. The orchestrator persists the cursor only after the cycle completes successfully.

paymentsState is a 3-way structure (one cursor per payments source) with a backward-compatible decoder for installs that ran the legacy single-watermark version of the connector — no manual migration step.

Known gaps#

  • Historical orders. Orders placed and fully filled before the connector was installed, or older than 30 days, fall outside Bitstamp's order_status/ retention and are not back-filled today. The per-fill rows exist in user_transactions/ as type=2 with order_id; aggregating them by order_id is the planned path. Tracked as future work in MAPPINGS.md §9.
  • No programmatic sub-account discovery. Bitstamp's API doesn't expose a "list my scopes" call. The deployment model is one connector per account scope, named explicitly via the name config field.
  • Cross-account transfer rows. Defensive mapping is in place (see Sub-account transfers) but the underlying API rows aren't surfaced today. Customers needing transfer reconciliation install one connector per sub-account.
FireblocksOpen Banking
On This Page
  • Prerequisites
  • Installation
  • Configuration fields
  • Capabilities
  • Workflow tree
  • Account model
  • Asset model
  • Payments — three-source union
  • Sub-account transfers
  • Orders
  • Conversions
  • Install-time enrichment
  • Metadata keys
  • Pagination and recovery
  • Known gaps