Receiving Wallet

Link a receiving wallet by scanning a QR with the mobile app: start a link session and poll its status.

Non-custodial balance

POSThttps://api.gobtcpay.com/public/api/v1.2/merchant/wallet/get-balance

The non-custodial merchant's own money, split into three non-overlapping levels: connectedWalletSats (confirmed on the reserved 0/0 address), paymentWalletsSats (confirmed on the per-payment addresses of payments that are detected, paid or cleared) and inFlightSats (still unconfirmed in the mempool, and signed, negative means an outgoing spend not yet in a block).

All figures come from the chain, never from the payment ledger: the platform does not see the merchant's outgoing transactions, so a ledger-derived number would be total receipts rather than a balance. When the chain cannot be reached the last known values are returned with balanceStale: true, they are never substituted with an internally computed amount. addressesTruncated: true means the merchant holds funds on more addresses than one refresh may query, so the totals are incomplete and must be presented as such.

Custodial merchants are rejected: their funds sit on the platform treasury address, see Custodial balance.

Authorization: merchantAuth

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /merchant/wallet/get-balance
cURL
curl -X POST "$BASE_URL/merchant/wallet/get-balance" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "connectedWalletSats": "string",
      "paymentWalletsSats": "string",
      "inFlightSats": "string",
      "walletLinked": true,
      "balanceAsOf": 1718012345,
      "balanceStale": true,
      "addressesTruncated": true,
      "signedPsbtSats": "cHNidP8BAHECAAAAAfYI0kF1pQ0i3xL0AAAAAAD9////AsBdAQAAAAAAFgAUdR5"
    }
  }
}

Wallets with a balance

POSThttps://api.gobtcpay.com/public/api/v1.2/merchant/wallet/list

Paginated list of the non-custodial merchant's addresses that currently hold money, the connected 0/0 address and the per-payment addresses under observation. Each row carries address, kind (connected / payment), derivationPath, paymentId (null for the connected wallet), confirmedSats and inFlightSats. Addresses with a zero total are omitted. Custodial merchants are rejected, they have no addresses of their own.

Authorization: merchantAuth

Body required

application/json
paginationobject

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /merchant/wallet/list
cURL
curl -X POST "$BASE_URL/merchant/wallet/list" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "pagination": {
    "limit": 20,
    "skip": 0
  }
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "items": [
        {
          "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "kind": "connected",
          "derivationPath": "string",
          "paymentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "confirmedSats": "string",
          "inFlightSats": "string"
        }
      ],
      "totalCount": 0
    }
  }
}

Build withdrawal PSBT

POSThttps://api.gobtcpay.com/public/api/v1.2/merchant/wallet/build-withdraw-psbt

Builds an unsigned single-sig P2WPKH PSBT spending UTXOs of the merchant's own fromAddresses to toAddress. Exactly one of amountSats (leaves change on a connected merchant address) or sendMax (sweeps everything selected minus fee, no change) must be set. The server never signs, the merchant signs every input on their own device, then confirms with Broadcast withdrawal. The returned summary (including the change address) is a display convenience only; the client must verify every output against the PSBT itself before signing. The job is reserved as prepared the moment this call returns and expires after a server-side TTL if never broadcast. Custodial merchants are rejected, they have no addresses of their own.

Authorization: merchantAuth

Body required

application/json
fromAddressesstring[]required
toAddressstringrequired

Destination Bitcoin address for the funds.

Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
amountSatsstring | number

Amount in satoshis.

sendMaxboolean

If true, sweeps the entire spendable balance to toAddress and ignores the amount.

Example:true
feeobjectrequired

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /merchant/wallet/build-withdraw-psbt
cURL
curl -X POST "$BASE_URL/merchant/wallet/build-withdraw-psbt" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "fromAddresses": [
    "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
  ],
  "toAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "amountSats": "string",
  "sendMax": true,
  "fee": {
    "feeRateSatVb": 50000
  }
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "psbtBase64": "cHNidP8BAHECAAAAAfYI0kF1pQ0i3xL0AAAAAAD9////AsBdAQAAAAAAFgAUdR5",
      "summary": {
        "toAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
        "amountSats": "string",
        "feeSats": "string",
        "feeRateSatVb": 50000,
        "vsize": 140,
        "change": {
          "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "valueSats": "string"
        },
        "inputs": [
          {
            "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
            "vout": 0,
            "value": 50000,
            "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
            "derivationPath": "string"
          }
        ]
      }
    }
  }
}

Broadcast withdrawal

POSThttps://api.gobtcpay.com/public/api/v1.2/merchant/wallet/broadcast

Hands over the withdrawal transaction the merchant signed and finalized locally, as raw signedTxHex, for the server to validate against the stored job and broadcast. Idempotent by jobId: wasAlreadySubmitted: true means the transaction was already broadcast and the returned txid is the stored one, not a new attempt.

Authorization: merchantAuth

Body required

application/json
jobIdstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
signedTxHexstringrequired
Example:0014751e76e8199196d454941c45d1b3a323f1433bd6

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /merchant/wallet/broadcast
cURL
curl -X POST "$BASE_URL/merchant/wallet/broadcast" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "signedTxHex": "0014751e76e8199196d454941c45d1b3a323f1433bd6"
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
      "wasAlreadySubmitted": true
    }
  }
}

Withdrawal job status

POSThttps://api.gobtcpay.com/public/api/v1.2/merchant/wallet/withdraw-status

Reads the current state of a withdrawal job by jobId, prepared / broadcasting / broadcasted / expired / failed, the same set the job row carries. A pure read with no side effects, safe to poll repeatedly; it exists because Broadcast withdrawal is idempotent but not informative, retrying it resubmits the same stored hex, but a job that is already expired or failed makes it fail outright rather than report a state. After a broadcast error, or after the app restarts and loses whatever local state it had, this is the only way left to learn whether the money moved. txid is set once the network has one for this job and null before that; summary (amount, fee, destination, change) and expiresAt let the app show the withdrawal without keeping any of it around locally, expiresAt goes back to null once the job is past waiting on the reservation TTL. A jobId belonging to a different merchant is rejected as NotFound, identically to one that does not exist.

Authorization: merchantAuth

Body required

application/json
jobIdstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /merchant/wallet/withdraw-status
cURL
curl -X POST "$BASE_URL/merchant/wallet/withdraw-status" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "status": "prepared",
      "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
      "expiresAt": 1718012345,
      "summary": {
        "toAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
        "amountSats": "string",
        "feeSats": "string",
        "feeRateSatVb": 50000,
        "change": {
          "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "valueSats": "string"
        }
      }
    }
  }
}