Invite sellers
Invite by email with their payout mode and your commission. member.joined tells you they're in.
A seller joins by accepting an invitation with their own QBitFlow account (or a new one). Until they accept, they aren't a seller.
- Your server → QBitFlow API: Invites them by email
- QBitFlow API → Your server: 201: the invitation and its link
- QBitFlow API → The seller: Emails the link
- Your server → The seller: Or you share it (sendEmail: false)
- The seller → QBitFlow API: Accepts, with their own account
- QBitFlow API → Your server: member.joined, with their userUuid
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 acceptconst 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)marketplace/main.go
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";email: QBitFlow emails the link there; the answer carries it too, for your own onboarding.sendEmail(defaulttrue):falsesends no invitation email; share thelinkyourself (your own page or email). Signing up through it still sends the seller QBitFlow's confirmation email. Test sellers accept from a real inbox too: invite addresses you control (you+seller1@yourdomain.com) once, and keep them (a key can't create accepted test sellers yet).holdFunds(required):trueholds their sales,falsepays them directly (the previous step).organizationFeePercent: your commission, 0 to 50.redirectUrl: where they land once they've accepted.
It answers 201 with the pending invitation and its link, valid for 7 days. A
new invitation of the same person replaces the open one; a current seller answers
409 already_joined. At most 50 invitations an hour, both modes
together.
Watch out
Sellers already on QBitFlow
An account can belong to several organizations: a seller already on QBitFlow accepts with their
existing account, and keeps their other organizations. Only the invited address's account can accept
(403 invitation_email_mismatch): invite another address they own.
Know when they join
When they accept, member.joined reaches your endpoint
(step 1):
member.joined· the body your endpoint receives
{ "createdAt": "2026-10-01T12:00:00Z", "data": { "invitationUuid": "01a0f755-f200-7000-8000-00000000000a", "organizationFeePercent": "5", "userUuid": "019eca82-5680-7b00-8000-0000000000b1", … 7 more fields }, "id": "evt_634bf7f1-010f-5f60-8232-06c30f013f28", "test": false, "type": "member.joined", "version": "v2"}data.invitationUuid: Matches the event to your invitation.data.organizationFeePercent: Your commission, as you invited them.data.userUuid: Store it: it's how you act for them (On-Behalf-Of).
Avoid
Trust the invitationUuid in your redirect URL
Do
Wait for member.joined
userUuid.Keep track
Open invitations, and revoking one (its link stops working; an accepted one answers 409):
invitations-list.sh
# The pending invitations.curl "https://api.qbitflow.app/v2/invitations?status=pending" \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Invitation ({ items, hasMore, nextCursor? })const page = await client.invitations.list({ status: InvitationStatus.Pending });for (const invitation of page.items) { console.log(`${invitation.uuid} ${invitation.email}, expires ${invitation.expiresAt}`);}page = client. invitations. list( status=qbitflow. InvitationStatus. PENDING)for invitation in page.items: print(invitation.uuid, invitation.email, "expires", invitation.expires_at)marketplace/main.go
invitations, err := client.Invitations.List(ctx, &qbitflow.InvitationListParams{ Status: qbitflow. InvitationStatusPending,})if err != nil { log.Fatal(err)}for _, inv := range invitations.Items { fmt.Println(inv.UUID, inv.Email, "expires", inv. ExpiresAt. Format( time. RFC3339))}$page = $client->invitations->list(new \QBitFlow\Params\InvitationListParams( status: \QBitFlow\Enums\InvitationStatus::PENDING));foreach ($page->items as $invitation) { echo "{$invitation->uuid} {$invitation->email}, expires {$invitation->expiresAt->format('Y-m-d')}\n";}invitations-revoke.sh
# Revokes an invitation not accepted yet: its link stops working.curl -X DELETE "https://api.qbitflow.app/v2/invitations/$INVITATION_UUID" \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: an Invitation// Only before it is accepted: its link stops working (204, nothing returned).await client. invitations. revoke( invitationUuid);console.log(`revoked ${invitationUuid}`);client. invitations. revoke( invitation_uuid) # 204: its link stops workingprint("revoked", invitation_uuid)marketplace/main.go
// 204: its link stops working.if err := client.Invitations.Revoke(ctx, invitationUUID); err != nil { log.Fatal(err) // 409 once accepted or revoked}fmt.Println("revoked", invitationUUID)$client->invitations->revoke( $invitationUuid); // a pending invitation only (204)echo "Revoked {$invitationUuid}: its link no longer works\n";Your sellers, a page at a time, or one by userUuid: their fee, trustedAt (absent while you hold their
funds) and the currencies they accept.
members-list.sh
# One page of the organization's members (an admin key).curl "https://api.qbitflow.app/v2/members?limit=20" \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Member ({ items, hasMore, nextCursor? })const page = await client.members.list({ limit: 20 });for (const member of page.items) { console.log(`${member.email}: fee ${member.organizationFeePercent} %`);}page = client.members.list()for member in page.items: print(member.user_uuid, member.email, f"fee {member. organization_fee_percent}%")marketplace/main.go
page, err := client.Members.List(ctx, &qbitflow. MemberListParams{Limit: 20})if err != nil { log.Fatal(err)}for _, m := range page.Items { fmt.Printf("%s %s %s, fee %s %%, trusted: %v\n", m.UserUUID, m.FirstName, m.Email, m.OrganizationFeePercent, m.TrustedAt != nil)}foreach ($client->members->iterate() as $member) { printf("%s %s %s: fee %s %%, %s\n", $member->userUuid, $member->firstName, $member->email, $member->organizationFeePercent, $member->trustedAt !== null ? 'trusted' : 'funds held');}members-get.sh
# One member, by their user uuid.curl "https://api.qbitflow.app/v2/members/$MEMBER_UUID" \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a Memberconst member = await client.members.get(memberUuid);console.log(`${member.firstName} ${member.lastName ?? ''} <${member.email}>`);console.log(`fee ${member.organizationFeePercent} %, trusted: ${member.trustedAt ?? 'no'}`);for (const currency of member.acceptedCurrencies) console.log(` accepts ${currency.code}`);member = client.members.get(member_uuid) # their user uuidprint(member.first_name, member.last_name, "trusted:", member.trusted_at is not None)for currency in member.accepted_currencies: # empty: their checkouts answer 409 print(" accepts", currency.code)marketplace/main.go
member, err := client.Members.Get(ctx, memberUUID) // their userUuidif err != nil { log.Fatal(err)}fmt.Printf("%s %s <%s>, fee %s %%, trusted: %v\n", member.FirstName, member.LastName, member.Email, member.OrganizationFeePercent, member.TrustedAt != nil)for _, c := range member.AcceptedCurrencies { fmt.Println(" accepts", c.Code)}$member = $client->members->get( $memberUuid); // the member's userUuidecho "{$member->firstName} " . ($member->lastName ?? '') . " <{$member->email}>, joined {$member->joinedAt->format('Y-m-d')}\n";foreach ($member->acceptedCurrencies as $currency) { echo " accepts {$currency->code}\n"; // none: their checkouts answer 409 merchant_not_ready}Check it
Invite one of your addresses: it's pending in
Dashboard/Members/Invitations. Accept it: member.joined
arrives, and the seller is listed by your code and in Dashboard/Members.