vellTRDocs
GuidesMarketplace & Split Payments

Marketplace & Split Payments

Advanced30 min

Onboard submerchants, split captures across sellers, and pay out to each seller.


Kvell's marketplace flow lets a single checkout split across multiple recipients β€” a platform selling on behalf of several sellers keeps its own cut and routes the rest straight to each seller's settlement balance, without ever touching the money itself. Splitting, submerchant onboarding, and payouts are the three moving pieces; this guide walks through all three with a full worked example.

How splits work

  • A payment session created with a splits array is a split payment. Each entry names a submerchantId and its amountMinor share; whatever is left after summing the splits stays with the platform (the parent merchant).
  • Every split recipient is a submerchant β€” a seller onboarded under your platform merchant account with its own identity, KYC status, and settlement balance. A submerchant only receives money once its KYC status is active; an unapproved submerchant listed in splits fails the session-create call.
  • splits[].amountMinor must sum to no more than the session amount. Kvell never infers a split β€” leave a seller out and they get nothing from that payment.
  • Everything you already use for a normal payment β€” 3DS, installments, capture, refund β€” runs unchanged underneath. Splitting only changes how the resulting proceeds are distributed internally. See Accepting Card Payments if you haven't built that flow yet.

Onboard a submerchant

Submerchant creation, identity capture, and KYC review happen in the merchant dashboard console β€” there's no signed public-API call for it. Once a submerchant clears KYC and is active, it participates in your integration two ways:

  • as a split recipient β€” pass its submerchantId inside splits on a payment session
  • as a payout recipient β€” pass its submerchantId to POST /v1/payouts

Each submerchant also gets its own API key, scoped to itself only. To confirm what kind of caller a key belongs to:

Shell
curl https://api.pay.kvell.group/v1/auth/whoami \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:00:00Z" \
  -H "X-Signature: <signature>"
JSON
{
  "subjectScope": "merchant",
  "merchantId": "mrc_8a1b2c3d4e5f",
  "apiKeyId": "key_708192a3b4c5"
}

subjectScope is "merchant" for your platform key and "submerchant" for a seller's own key. A submerchant's KYC decision reads the same way yours does β€” GET /v1/kyc/status, covered in Going Live.

Worked example: a 1,200 TRY order split between two sellers

A marketplace platform sells a 1,200.00 TRY (120000 kuruş) order made up of items from two sellers. Seller A's share is 700.00 TRY, seller B's is 400.00 TRY, and the platform keeps the remaining 100.00 TRY as its take rate.

PartysubmerchantIdShare (kuruş)Share (TRY)
Seller Asub_7f3a9c2170000700.00
Seller Bsub_2b88e04d40000400.00
Platform (remainder)β€”10000100.00
Total1200001,200.00

1. Create the split payment session

Shell
curl -X POST https://api.pay.kvell.group/v1/payments/sessions \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:00:00Z" \
  -H "X-Signature: <signature>" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionId": "ORDER-2026-4521",
    "amount": 120000,
    "currency": "TRY",
    "description": "Order #4521 (2 sellers)",
    "returnUrl": "https://marketplace.example.com/return",
    "splits": [
      { "submerchantId": "sub_7f3a9c21", "amountMinor": 70000 },
      { "submerchantId": "sub_2b88e04d", "amountMinor": 40000 }
    ]
  }'
JSON
{
  "sessionId": "4d4e8a9c-1f2a-7b8c-9d0e-1f3f1c9a2e6b",
  "checkoutUrl": "https://pay.kvell.group/s/4d4e8a9c-1f2a-7b8c-9d0e-1f3f1c9a2e6b",
  "expiresAt": "2026-04-13T10:30:00Z",
  "amount": 120000,
  "currency": "TRY",
  "status": "active"
}

Redirect the buyer to checkoutUrl. Everything downstream β€” 3DS, installments, capture β€” behaves exactly like a normal hosted checkout.

2. Receive the capture webhook

When the buyer completes payment, Kvell fires the same payment.captured event you'd get for any card charge:

Shell
X-Kvell-Event: payment.captured
X-Kvell-Event-Id: evt_5b6c7d8e9f0a
X-Kvell-Delivery: whd_0f1a2b3c4d5e
X-Kvell-Signature: sha256=8f0e1d2c3b4a59687a1e2d3c4b5a6978c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f
X-Request-Id: 3c9e6a1b-7f2d-4e8a-b1c9-0d2f3a4b5c6d
JSON
{
  "eventId": "evt_5b6c7d8e9f0a",
  "eventType": "payment.captured",
  "occurredAt": "2026-04-13T10:00:05Z",
  "merchantId": "mrc_8a1b2c3d4e5f",
  "payload": {
    "paymentId": "pay_1a2b3c4d5e6f",
    "transactionId": "ORDER-2026-4521",
    "amount": 120000,
    "currency": "TRY",
    "status": "captured"
  }
}

The event carries the order total, not a per-seller breakdown β€” Kvell tracks each seller's share internally. Fetch GET /v1/payments/{id} if you need to show the split back to the buyer or reconcile it against your own order records.

3. Pay each seller out

Once a seller's share has settled, disburse it with a submerchant payout. The destination IBAN and account holder are sourced from the submerchant's own record β€” you only pass the amount:

Shell
curl -X POST https://api.pay.kvell.group/v1/payouts \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:05:00Z" \
  -H "X-Signature: <signature>" \
  -H "Content-Type: application/json" \
  -d '{
    "submerchantId": "sub_7f3a9c21",
    "rail": "fast",
    "amountMinor": 70000,
    "currency": "TRY"
  }'
JSON
{
  "payoutId": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
  "status": "pending",
  "submerchantId": "sub_7f3a9c21"
}

Repeat for seller B with amountMinor: 40000. Each payout resolves asynchronously β€” poll GET /v1/payouts/{id} or listen for payout.succeeded / payout.failed on your webhook endpoint. Testing in the Sandbox covers the payout-success / payout-declined scenarios so you can exercise both outcomes before going live.

Rails available for submerchant payouts

rail accepts the same three values as a regular payout:

RailDestinationNotes
fastTR IBANInstant, 24/7
eftTR IBANInterbank, business hours
cardCard tokenPush-to-card (OCT)

Refunds on a split payment

Refund a split payment the same way you refund any payment β€” POST /v1/payments/{id}/refund. Kvell reverses the refunded amount proportionally across the original owners (platform and each submerchant), so nobody's share is refunded out of proportion to what they received. A partial refund on the example above reduces each party's held balance in the same ratio the original capture credited it.

Checking available balance before paying out

Before disbursing, confirm what's actually outstanding:

Shell
curl https://api.pay.kvell.group/v1/payouts/outstanding \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:00:00Z" \
  -H "X-Signature: <signature>"
JSON
{
  "merchantId": "mrc_8a1b2c3d4e5f",
  "pendingMinor": 250000,
  "currency": "TRY"
}

For a large number of sellers, batch the disbursement instead of calling POST /v1/payouts in a loop β€” see Bulk payouts for many sellers below.

Bulk payouts for many sellers

If you're paying out dozens of submerchants on the same run β€” a weekly seller settlement, for example β€” upload a draft instead of issuing payouts one at a time:

Shell
curl -X POST https://api.pay.kvell.group/v1/payout-drafts \
  -H "X-Api-Key: pk_live_a1b2c3d4e5f6g7h8" \
  -H "X-Timestamp: 2026-04-13T10:00:00Z" \
  -H "X-Signature: <signature>" \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "sto_5a6b7c8d",
    "rows": [
      { "rail": "fast", "dest": "TR330006100519786457841326", "holderName": "Seller A", "amountMinor": 70000, "currency": "TRY" },
      { "rail": "fast", "dest": "TR120006200119000006672315", "holderName": "Seller B", "amountMinor": 40000, "currency": "TRY" }
    ]
  }'
JSON
{
  "draftId": "drf_8192a3b4c5d6",
  "status": "draft",
  "rowCount": 2,
  "validCount": 2,
  "invalidCount": 0,
  "createdAt": "2026-04-13T10:00:00Z"
}

Review the draft with GET /v1/payout-drafts/{id}, then call POST /v1/payout-drafts/{id}/approve β€” Kvell fans each validated row out to a real payout, at most one per row. A draft accepts up to 1,000 rows, one store per draft, and dest (the IBAN or card token) is never echoed back in any response.

Errors specific to marketplace payouts

CodeHTTPMeaning
KVELL-PO-4093422Payout exceeds the single-payout or daily limit
KVELL-PO-4094422Insufficient settled balance for this payout
KVELL-PO-4095409Submerchant is not active yet (KYC incomplete or suspended)
KVELL-PO-4096422Submerchant has no bank account on file β€” resolve this in the dashboard first

Next steps

GuideWhat it covers
Accepting Card PaymentsPlain (non-split) hosted checkout, 3DS, refunds
WebhooksDelivering payment.captured and payout.* events reliably
Testing in the SandboxScripting the whole split β†’ capture β†’ payout flow risk-free
API referenceFull Marketplace and Payouts endpoint docs