Back to BlogIndustry Guides

Marketplace Seller Onboarding and KYC: Build and Test the Gates

Rupak Amin

Founder & Lead Engineer, RAITHub

10 min read

Marketplace seller onboarding is a state machine with two gates: can this seller list products, and can this seller receive a payout. Know-your-customer (KYC) identity checks usually run through your payment provider's connected-account flow, not your code, so your job is to drive seller status from its verification webhooks and refuse listing or payout until the gate is open. Test that an unverified seller is never paid.

If you would rather have it built for you, see how RAITHub would build this below.

This post is about the onboarding and verification gates. The payout mechanics (splitting money, holding funds, escrow) are covered in Stripe Connect marketplace payments and split payments and escrow; general identity and anti-money-laundering concepts are in the KYC and AML integration guide.

Who actually does the KYC on a marketplace?

Usually your payment provider. A connected-account model (for example Stripe Connect, or an equivalent) runs the identity and business verification as part of onboarding a seller to receive funds, because the provider is the regulated party that moves the money. That is a deliberate boundary: you collect what the seller needs to start, hand them to the provider's verification flow, and react to the result. RAITHub builds the marketplace and this integration; it is not a licensed KYC or anti-money-laundering vendor, and the verification itself stays with the provider. This is general information, so confirm your own obligations with a compliance adviser.

StepWho owns itWhat you build
Collect business and bank detailsYou, then the providerAn onboarding form, or a redirect to the provider's hosted onboarding
Identity and business verification (KYC)The payment providerNothing: you react to the result via webhook
Deciding a seller can listYouA gate: your own review plus a minimum provider status
Deciding a seller can be paidYou, bound by the providerA gate: payouts enabled on the connected account

What does the onboarding state machine look like?

A seller moves through states, and each transition is driven by either your review or a provider webhook. Keep the states explicit, because "can list" and "can be paid" are different questions with different answers at different times. A seller may be allowed to list while their bank verification is still pending, or may be blocked from payout after the provider flags a problem months later.

CREATE TYPE seller_status AS ENUM (
  'invited',            -- account created, nothing submitted
  'details_submitted',  -- business and bank details sent to the provider
  'verifying',          -- provider is running KYC
  'verified',           -- provider cleared the account
  'rejected',           -- provider or you rejected the seller
  'suspended'           -- previously active, now blocked
);

CREATE TABLE sellers (
  id                 bigserial PRIMARY KEY,
  status             seller_status NOT NULL DEFAULT 'invited',
  provider_account   text,                      -- the connected-account id
  charges_enabled    boolean NOT NULL DEFAULT false,  -- provider: can take payment
  payouts_enabled    boolean NOT NULL DEFAULT false,  -- provider: can be paid out
  listing_approved   boolean NOT NULL DEFAULT false,  -- your own review
  updated_at         timestamptz NOT NULL DEFAULT now()
);

The two booleans from the provider (charges_enabled, payouts_enabled) and your own listing_approved are the gates. The status enum is the human-readable summary; the gates are what the code checks.

How do you keep seller status in sync with the provider?

From webhooks, idempotently. When the provider finishes or updates verification, it sends an account-updated event. Your handler reads the authoritative flags off the event (or re-fetches the account), writes them, and recomputes the status. Never infer "verified" from the seller clicking a button at the end of onboarding; the only source of truth for charges and payouts is the provider.

// On the provider's account-updated webhook. Idempotent: the same event
// can arrive twice, and re-fetching gives the current truth either way.
export async function onAccountUpdated(accountId: string, db: Db) {
  const account = await provider.accounts.retrieve(accountId)   // authoritative
  await db.sellers.update(
    { provider_account: accountId },
    {
      charges_enabled: account.charges_enabled,
      payouts_enabled: account.payouts_enabled,
      status: deriveStatus(account),
      updated_at: new Date(),
    },
  )
}

Webhooks can arrive out of order or more than once, so the handler must be safe to re-run and should trust a re-fetch over the event body. The general discipline, verifying signatures, handling retries, acting idempotently, is in testing payments and webhooks.

Where exactly do the gates go?

At the two actions that matter, checked on the server:

  • Listing gate: a seller can create or publish a product only when listing_approved is true (your review) and the provider allows charges. A seller whose products appear but who cannot take payment is a dead end for buyers.
  • Payout gate: money is released to a seller only when payouts_enabled is true. If a sale completes before payouts are enabled, hold the seller's share and release it when the gate opens, rather than failing the buyer's order.

Both gates are server-side permission checks, the same isolation discipline as any multi-tenant system, where one seller must never act as another; see how one tenant ends up seeing another's data.

How do you test onboarding and the gates?

The provider's verification is external, so test against its sandbox and simulated webhook events, and unit-test your own gate logic in isolation. The non-negotiable test is that an unverified seller is never paid.

import { describe, it, expect } from 'vitest'
import { canList, canPayout, deriveStatus } from './gates'

describe('seller gates', () => {
  it('blocks payout until the provider enables payouts', () => {
    const seller = { payouts_enabled: false, charges_enabled: true, listing_approved: true }
    expect(canPayout(seller)).toBe(false)
  })

  it('blocks listing until your review passes, even if the provider cleared KYC', () => {
    const seller = { payouts_enabled: true, charges_enabled: true, listing_approved: false }
    expect(canList(seller)).toBe(false)
  })

  it('an account-updated event that disables payouts suspends the seller', () => {
    expect(deriveStatus({ charges_enabled: false, payouts_enabled: false, was: 'verified' }))
      .toBe('suspended')
  })
})

End to end, drive a seller through the provider's sandbox onboarding, fire the simulated account-updated webhook, and assert the seller can list only after approval and can be paid only after payouts are enabled. A pre-launch pass should also cover a sale that completes before payout is enabled, as in the marketplace QA checklist.

Buy, build or hire?

OptionChoose this whenThe catch
A marketplace platform (Sharetribe and similar)A standard marketplace whose onboarding and payout model fits the platformBespoke listing gates, your own review steps or local payment providers often need custom work; see Sharetribe vs custom
The provider's hosted onboarding plus thin glueYou use a connected-account provider and just need to react to its webhooksYou still own the gates, the webhook idempotency and the tests
Custom buildListing rules, review steps, payout holds or multiple providers are specific to your marketYou own the state machine and the test that keeps an unverified seller unpaid

How long does it take to build yourself, and what is the risk?

For a developer integrating a connected-account provider into an existing marketplace, our estimate is 2 to 4 weeks: onboarding, the webhook sync, both gates, payout holds and the tests. The main risk is treating the end of onboarding as proof of verification; it is not, and only the provider's flags are. The second risk is webhooks arriving out of order, which a re-fetch and idempotent handler solve. What needs a certified or licensed vendor, the KYC and anti-money-laundering decision itself, stays with the payment provider; this is general information, so confirm your obligations with a compliance adviser.

Why RAITHub for this

  • Role gating in production. Sundor Skin, a B2B wholesale platform RAITHub built, runs 12 staff roles from 88 permission codes with row-level security, across 146 PostgreSQL tables and 530+ tests, so each actor sees and does only what their gate allows. See the Sundor Skin case study.
  • Webhook-driven money paths. On TheSkinProof, the founder's own venture built and run by RAITHub, payment events are verified and handled idempotently before money moves.
  • The gates are tested. The listing and payout gates get unit and end-to-end tests gated in CI, so a refactor cannot open a gate by accident.

When you don't need us

  • A platform fits. If a marketplace platform's onboarding and payouts match your market, configure it.
  • Standard connected accounts. A single provider with hosted onboarding may need only thin glue your own developer can write.
  • You only need the gates. The state machine above is a fair start.

How RAITHub would build this

  • Onboarding: collect details, hand off to the provider's hosted verification, and track the seller's status.
  • Webhook sync: idempotent handlers that trust the provider's flags, with a re-fetch for the authoritative state.
  • Gates: server-side listing and payout gates, with payout holds for sales that complete before payouts are enabled.
  • Tests: sandbox onboarding, simulated webhooks and unit tests that keep an unverified seller unpaid, gated in CI.

Timeline: adding this to an existing marketplace is backend work, typically 6 to 12 weeks; a new marketplace MVP fits the 4 to 6 week fixed-scope range. See API and backend development, SaaS development and the ecommerce industry page.

You receive: automated tests and CI covering both gates, handover docs, and full IP in your name under NDA.

Next step: book the free 15-minute technical audit with your payment provider and onboarding rules, and we will follow up with a written fixed quote.

Frequently asked questions

Who does the KYC on a marketplace, me or the payment provider?

Usually the payment provider, through its connected-account onboarding, because it is the regulated party that moves the money. You build the onboarding entry point and react to the provider's verification result. Confirm your own obligations with a compliance adviser.

What is the difference between "can list" and "can be paid"?

They are separate gates. A seller may be allowed to list products after your review while bank verification is still pending, and may later be blocked from payout if the provider flags a problem. Model and check them independently.

How do I keep seller status in sync with the provider?

From the provider's account-updated webhooks, handled idempotently. Re-fetch the account to get the authoritative charges and payout flags, write them, and recompute status. Never infer verification from the seller finishing onboarding.

What happens if a sale completes before the seller can be paid?

Hold the seller's share rather than failing the buyer's order, and release it when the payout gate opens. Test this path, because it is easy to miss and it strands money.

Does RAITHub do the identity verification itself?

No. RAITHub builds the marketplace and the integration; the KYC and anti-money-laundering verification stays with the payment provider or a licensed vendor. RAITHub is not a certified KYC vendor. This is general information; confirm your obligations with a compliance adviser.

Can I use a marketplace platform instead of building this?

Often yes. If a platform's onboarding and payout model fits your market, configure it. Build custom when your listing rules, review steps, payout holds or payment providers are specific.

marketplace seller onboardingseller kycstripe connect onboardingpayout gatingverification state machinemarketplace

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.