Blacksheep: regulated fiat ↔ crypto payments API (AU/NZ)
Move money between bank rails and crypto programmatically — quote, swap, withdraw, webhooks — with request signing instead of bearer tokens.
devrels.xyz/a/199Blacksheep is a regulated stablecoin / multicurrency payments product (Australia AUSTRAC + New Zealand FSP) with a public API so you can quote, swap, and withdraw between fiat and crypto programmatically — bank rails on one side, self-custody wallets on the other.
Product: blacksheep.money. Docs: docs.blacksheep.money/docs. App: my.blacksheep.money. API base: https://api.blacksheep.money. X: @Blacksheepmoney.
Related: crypto cards · Solana neobank how-to · RedotPay.
What the API is for
Docs pitch: move money between fiat and crypto programmatically — manage external bank and wallet destinations, and receive webhooks for state changes. Marketing emphasizes local rails (e.g. NPP for AUD, ACH for USD), direct bank ↔ wallet paths, and keeping a model where users can maintain custody of funds outside the platform when withdrawing to wallets.
This is a regulated payments / FX API, not a Solana DEX aggregator. Solana shows up as a wallet address family (sol) for crypto withdrawals when the asset/chain pair supports it.
Domain model
| Resource | Meaning |
|---|---|
| Account | Organisation or individual owning the API key; scopes all balances, txs, destinations, webhooks |
| Balance | Currency held under the account (accrues from deposits/swaps; spent by withdrawals/swaps) — not created directly |
| Transaction | Unit of movement (deposit, withdraw, buy/sell, swap) |
| External account | Bank account (fiat) or wallet address (crypto); created once, reused by id |
| Quote | Indicative rate before initiateSwap |
| Webhook | HTTPS URL for signed transaction.created / transaction.updated |
Transaction types and states
| Type | Direction | API |
|---|---|---|
deposit | External → Blacksheep balance | Incoming (no initiate call) |
withdraw | Balance → external account | transaction.submitWithdraw |
buy / sell | Fiat ↔ crypto | transaction.initiateSwap |
swap | Held currency A → B | transaction.initiateSwap |
| Status | Meaning |
|---|---|
PENDING | Accepted and queued |
COMPLETED | Settled |
FAILED | Could not complete |
CANCELLED | Cancelled before settlement |
Prefer webhooks over polling. No client idempotency key on mutations — a retry is a new transaction; guard duplicates on your side.
HTTP shape
| Item | Detail |
|---|---|
| Base URL | https://api.blacksheep.money |
| Method | All public endpoints POST + JSON (empty body when no input) |
| Path style | tRPC-style names, e.g. POST /v1/transaction.initiateSwap |
| Success | { "data": ... } |
| Error | { "error": { "code", "message" } } (auth failures may be flat 401) |
| Groups | account.*, quote.*, transaction.* |
Money encoding
Amounts are never floats. Wire format:
{
"value": "1500000",
"code": "USDC",
"decimals": 6,
"chain": "ethereum"
}value is a string-encoded integer in the smallest unit; crypto also carries chain. Responses may add a metadata string field. Use BigInt / decimal libs — not Number.
Authentication (Ed25519)
No bearer tokens. Dashboard (Settings → Dev space) creates an Ed25519 keypair in-browser, registers the public key, and shows once:
| Env | Role |
|---|---|
BSK_KEY_ID | Public id → Bs-Key-Id header |
BSK_SIGNING_KEY | PKCS#8 DER private key, standard base64 — local only, never sent |
| Header | Value |
|---|---|
Bs-Key-Id | Key id |
Bs-Timestamp | Unix seconds |
Bs-Nonce | 16 random bytes, standard base64 |
Bs-Signature | Base64 Ed25519 over the signing string |
Content-Digest | sha-256=:<b64>: when body present; omit if no body |
Signing string joins length-prefixed fields with : (length first prevents delimiter injection):
{len(keyId)}:{keyId}:{len(ts)}:{ts}:{len(nonce)}:{nonce}:{METHOD}:{len(path)}:{path}:{Content-Digest}Docs ship a Node.js signRequest helper. First call example: POST https://api.blacksheep.money/v1/account.balance.getMany. Use standard base64 (not base64url) for nonce, signature, and digest.
Core flows
Swap
quote.get → (show user) → transaction.initiateSwap
→ webhook transaction.updated (COMPLETED | FAILED | …)// POST /quote.get — currency_out.value "0" means "price the output"
{
"currency_in": { "value": "100000000", "code": "USDC", "decimals": 6, "chain": "ethereum" },
"currency_out": { "value": "0", "code": "AUD", "decimals": 2 }
}
// POST /transaction.initiateSwap — both sides non-zero; out from quote ask
{
"currency_in": { "value": "100000000", "code": "USDC", "decimals": 6, "chain": "ethereum" },
"currency_out": { "value": "154700", "code": "AUD", "decimals": 2 },
"external_accounts": { "from": null, "to": null }
}
// → { "data": "<transaction-uuid>" }Refresh quotes every 5–10s on a confirmation UI. Engine validates implied rate vs market then re-quotes at execution. Native crypto↔crypto direct pairs are rejected — bridge via USDC/USDT. Minimum sizes and unsupported codes return BAD_REQUEST.
Withdraw to bank or wallet
Register external accounts once; withdraw with transaction.submitWithdraw + external_account_id. Wallet list: account.external.web3.getAll. Wallet metadata.type is one of:
| Type | Address family |
|---|---|
evm | EVM |
tvm | TVM |
sol | Solana |
btc | Bitcoin |
Match wallet family to currency_out.chain. Network fees come out of the requested amount. On-chain refs appear on the transaction when available (external_reference).
Webhooks
| Type | When | Payload |
|---|---|---|
transaction.created | New tx | { object } |
transaction.updated | Status/fields change | { object, previous_attributes } |
Register HTTPS URLs in the dashboard (not via API key endpoints). Deliveries carry webhook-id, webhook-timestamp, webhook-signature (v1a,<b64>). Signature covers id.timestamp.rawBody with the webhook’s Ed25519 public key. Respond 2xx within 20s; verify, apply ~5 minute timestamp window, and dedupe on webhook-id.
Builder checklist
| Step | Action |
|---|---|
| 1 | Create account + KYC on my.blacksheep.money |
| 2 | Create API key; store BSK_KEY_ID + BSK_SIGNING_KEY securely |
| 3 | Implement signRequest (length-prefixed Ed25519 string) |
| 4 | account.balance.getMany as smoke test |
| 5 | Register webhook URL; verify signatures |
| 6 | Fund balances; run quote → initiateSwap; then withdraw guides |
| 7 | For Solana wallets: register WEB3 external account with type: sol; match chain on withdraw |
Resources
- Docs home · API reference · Authentication
- Guides: Swapping · Withdraw to wallet · Withdraw to bank · Webhooks
- Playground
- Blacksheep on DevRels
Bottom line
Blacksheep gives ANZ-regulated fiat rails plus a clean public API: Ed25519-signed POSTs, strict money objects, quote → swap → webhook, and withdrawals to banks or multi-family wallets including Solana. For builders shipping AUD/NZD ↔ stablecoin product paths, start at auth + balances, then swap and withdraw guides — treat live chain/asset support as dashboard/docs truth, not assumptions from this summary.
Keep reading
Stablecoin rails are useless at the last mile if users only have cash. MoneyGram Ramps is the Foundation’s bid to wire that last mile into SDP payments.
The pay.sh catalog is full of global APIs. None of them own Australia. Milypay wraps ABR, ASIC, G-NAF, ATO super, BOM weather, AusPost, and AusPayNet BSB as HTTP 402 endpoints agents can discover, pay, and call — in AUD stablecoins on Solana.
Sphere Labs sells SpherePay: one REST API for moving money between USD/EUR/BRL bank rails and USDC/USDT/EURC on Solana and other chains. The Solana-native bank API with PIX, Offloader Wallets, and Onramper virtual accounts. Integration model, code shapes, and honest caveats.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
