Concepts

Marketplace: members, commission and payouts

Your sellers are members. Paid directly, each sale splits on-chain; held, it all comes to your wallet until you release it.

settled · split on-chain ✓

Illustrative example: a 10% platform fee on a 100.00 USDC payment.
Payout, chosen per sellerNever held by QBitFlow
  • Paid directly: Each of their sales pays your fee to your wallet, and their share goes straight to theirs.
  • Held by you: Each of their sales pays your fee, and their share waits in your wallet until you release it to theirs.

Members

  • Invite a seller with POST /invitations (your organization key, in its mode): their email, role: "user", whether you hold their funds (holdFunds) and your commission (organizationFeePercent). QBitFlow emails the link (unless sendEmail: false); the answer carries it too.
  • They accept with their own QBitFlow account, or a new one: member.joined brings their userUuid. Store it: On-Behalf-Of takes it. They're a seller only once accepted, in that mode.
  • They add their own wallet. Only they can: your team can't change it, even acting as them.
  • You sell for them with On-Behalf-Of: <userUuid>, in their space (spaces).
Invite a seller

members-invite.sh

# An organization key (an admin's), in the key's mode. holdFunds holds their payments until you# trust them; organizationFeePercent is your commission. The link is also emailed ("sendEmail":# false to share it yourself): they are a member once they accepted (the member.joined webhook).curl https://api.qbitflow.app/v2/invitations \  -H "X-API-Key: $QBITFLOW_API_KEY" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: $(uuidgen)" \  -d '{    "email": "seller@example.com",    "role": "user",    "holdFunds": true,    "organizationFeePercent": "10",    "redirectUrl": "https://shop.example.com/sellers/welcome"  }'# 201: a CreatedInvitation: the invitation's fields and "link", the link to accept
const created = await client.invitations.create({	email: 'seller@example.com',	holdFunds: true, // hold their payments until you trust them (always sent)	organizationFeePercent: '10', // your commission on their payments: '0' to '50' %	redirectUrl: 'https://shop.example.com/sellers/welcome',});// QBitFlow also emails them a link of their own. They are a member once they accepted (the// member.joined webhook).console.log(`invitation ${created.uuid}: ${created.link}`);
created = client.invitations.create(    email="seller@example.com",    hold_funds=True,  # hold their payments until you trust them (always sent)    organization_fee_percent="10",  # your commission on their payments: "0" to "50" %    redirect_url="https://shop.example.com/sellers/welcome",)# The seller exists once they accept: wait for the member.joined webhook.# created.uuid is what member.joined's invitation_uuid names; its user_uuid is the seller.# QBitFlow also emails them a link of their own.print("invitation", created.uuid, "link:", created.link)
created, err := client.Invitations.Create(ctx, &qbitflow.CreateInvitationParams{	Email:                  "seller@example.com",	HoldFunds:              true, // hold their payments until Members.Trust (always sent)	OrganizationFeePercent: "10", // your commission on their payments: "0" to "50" %	RedirectURL:            "https://shop.example.com/sellers/welcome",})if err != nil {	log.Fatal(err) // 409 already_joined for a member}// QBitFlow also emails the seller a link of their own. The seller exists once they// accepted: wait for member.joined.fmt.Println("invitation", created.UUID, "link:", created.Link)
$created = $client->invitations->create(new \QBitFlow\Params\CreateInvitationParams(	email: 'seller@example.com',	holdFunds: true,              // hold their payments until you trust them (always sent)	organizationFeePercent: '10', // your commission on their payments: "0" to "50" %	redirectUrl: 'https://shop.example.com/sellers/welcome',));// QBitFlow also emails them a link of their own.echo "Invitation {$created->uuid}: {$created->link}\n";

No OAuth step: the invitation is their consent. Your organization's endpoints receive every seller's events by default, each naming the seller in userUuid. A seller whose account already belongs to another organization accepts with it: an account may belong to several (sellers already on QBitFlow).

Your commission

organizationFeePercent (0 to 50 %) is taken from what remains after QBitFlow's fee, on each of the seller's payments, and paid to your wallet in the same transaction (withheld from what you owe them instead, when you hold their funds). Each payment records it (settlement.organizationFee, settlement.split.organization). With your checkout.customerPaysProcessingFee on (it applies to every seller), buyers pay QBitFlow's fee on top, so your commission is on the full listed price: a $100 sale at 20 %, you $20, the seller $80. The invitation states it that way.

  • It's paid to your wallet on the payment's chain, so your sellers sell only where you have one: removing your wallet of a chain removes that chain's currencies from every seller (subscriptions already signed keep billing, without your commission).
  • A checkout keeps its commission; a running subscription's next bills take the current one.
  • While you take a commission or hold their funds, a seller's currencies stay within yours.

Change it with PATCH /members/:userUuid: nothing tells the seller, so tell them. A seller, as GET /members/:userUuid reads them:

GET /members/:userUuid · the request and its answer

request.sh

curl "https://api.qbitflow.app/v2/members/$MEMBER_UUID" \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a Member

200 · response.json

{  "acceptedCurrencies": […],  "email": "alice@example.com",  "firstName": "Alice",  "joinedAt": "2026-06-15T09:00:00Z",  "lastName": "Martin",  "organizationFeePercent": "5",  "spaceUuid": "019eca82-5680-7b00-8000-0000000000b2",  "test": false,  "trustedAt": "2026-07-01T09:00:00Z",  "userUuid": "019eca82-5680-7b00-8000-0000000000b1"}
Its example: the member.removed event's data, the same Member.
  1. organizationFeePercent: Your commission on their payments.
  2. trustedAt: Absent while you hold their funds.
  3. userUuid: What On-Behalf-Of takes.

Held or direct payouts

WhatPaid directlyHeld by you
Set withholdFunds: false, or once trustedholdFunds: true (trustedAt absent until trusted)
A sale paysthe seller's wallet, your commission to yoursyour wallet: their share is owed to them
The seller needs a walletbefore their first salebefore you release
Subscriptionssold as usualnot sold: their subscription checkouts answer 409 merchant_not_ready (details.reason: "fundsHeld")
Refundsfrom the seller's walletfrom your wallet, borne by the seller up to what you owe them for that payment
  1. Held in your walletA saleOwed to the seller: their share, your commission withheld
  2. A negative lineA refund, maybeSent from your wallet; what you owe drops
  3. heldFunds.released ✓You releaseEverything held, in one transfer, signed with your wallet
  4. Paid directlyYou trust themTheir new payments go straight to their wallet
  • Held funds are what you owe: each payment's net share, less what refunds of those payments sent back (held refunds). GET /members/:userUuid/held-funds lists a seller's lines and totalUsd, a decimal string; GET /members/held-funds, every seller you owe.
  • Release pays everything held for that seller in this mode, whatever chain each sale was on, at today's price, in the token you pick, sent on its chain, signed once by your wallet in the dashboard. Both sides' endpoints get heldFunds.released. Releasing from the SDKs isn't available yet.
  • Trust a seller (POST /members/:userUuid/trust): their new payments go straight to them. What's held stays held until released, and a held seller can't sell subscriptions, so trust them to open that.
Read members' held funds

members-held-funds.sh

# What the organization holds for its members (their payments while untrusted), the longest held# first. One member's lines: GET /members/$MEMBER_UUID/held-funds.curl https://api.qbitflow.app/v2/members/held-funds \  -H "X-API-Key: $QBITFLOW_API_KEY"# 200: [ { "userUuid": "…", "totalAmount": 98.35, "count": 2, "oldestAt": "…" } ]
// What the organization holds for its members (their payments while untrusted), the longest// held first.const all = await client.members.listHeldFunds();for (const owed of all) {	console.log(`${owed.userUuid}: ${owed.totalUsd} USD owed over ${owed.count} lines`);} const held = await client.members.getHeldFunds(memberUuid);for (const ledger of held.ledgers) {	// owed: what the line counts for in a release (negative for a refund).	console.log(		`  ${ledger.type} ${ledger.owed} ${ledger.currency.symbol} (${ledger.owedUsd} USD)`	);}console.log(`held for this member: ${held.totalUsd} USD`);
# Every member you hold funds for, the longest held first.for summary in client.members.list_held_funds():    print(summary.user_uuid, f"{summary.total_usd} USD over {summary.count} lines") held = client.members.get_held_funds(member_uuid)  # one member, line by lineprint(f"owed to the seller: {held.total_usd} USD over {len(held.ledgers)} lines")
// What the organization holds for its members (their payments while untrusted).// The longest held first.all, err := client.Members.ListHeldFunds(ctx)if err != nil {	log.Fatal(err)}for _, s := range all {	fmt.Printf("%s: %s USD over %d lines\n", s.UserUUID, s.TotalUSD, s.Count)} held, err := client.Members.GetHeldFunds(ctx, memberUUID)if err != nil {	log.Fatal(err)}fmt.Printf("owed to %s: %s USD over %d lines\n", memberUUID, held.TotalUSD, len(held.Ledgers))
foreach ($client->members->listHeldFunds() as $owed) { // every member you owe, the longest held first	echo "{$owed->userUuid}: {$owed->totalUsd} USD over {$owed->count} lines\n";} $held = $client->members->getHeldFunds($memberUuid);printf("Owed to %s: %s USD over %d lines\n", $memberUuid, $held->totalUsd, count($held->ledgers));

Removing a seller

DELETE /members/:userUuid ends their membership in that mode: checkouts take no more payments, subscriptions are cancelled, keys revoked, products hidden, and member.removed is sent. Their records stay with you, their account with them. Refused while you hold their live funds (409 held_funds_pending): release first.

More on marketplace payments.