Acting on Behalf of a Sub-merchant

A marketplace's API key has two personalities:

  1. The marketplace itself — used to call the Connect-management routes (/api/connect/*).
  2. A specific sub-merchant — used for everything that touches money: payments, payouts, customers, payment links, subscriptions, and so on.

Switching between them is a single HTTP header.

The X-On-Behalf-Of header

X-On-Behalf-Of: usr_01JABCDEF…

Set the value to the subMerchantId you want to act as. The header is case-insensitive; both X-On-Behalf-Of and x-on-behalf-of work.

When a marketplace API key calls a merchant route:

  • Without the header → request is rejected with 400 ON_BEHALF_REQUIRED_FOR_MARKETPLACE. Marketplaces have no first-class merchant balance, so there's no sensible default sub-merchant.
  • With a valid header → the request is treated as if the sub-merchant itself made it. The customer record, the payment's owner, the wallet to credit, and every other side-effect resolve to the sub-merchant.

Non-marketplace API keys (regular merchants, individuals) must not send the header. If they do, the request is rejected with 403 ON_BEHALF_FORBIDDEN_CALLER_TYPE.

Which routes accept the header?

Every standard Inflow merchant API endpoint — payments, hosted checkout, payment links, customers, payment methods, subscriptions, withdrawals, account management, and so on. You don't need to learn a new API surface to operate as a sub-merchant; you just call the same routes you would call as a merchant and add the header.

The only exceptions are the Connect-management routes themselves (/api/connect/*), which always operate on the calling marketplace and do not accept the header.

If you're unsure whether a specific route accepts the header, calling it without the header from a marketplace key is a clean check: you'll either get 400 ON_BEHALF_REQUIRED_FOR_MARKETPLACE (it does, you just need to set it) or normal route behavior (it doesn't).

Validity rules

For a marketplace + X-On-Behalf-Of: <id> request to succeed, all of these must hold:

  1. The calling marketplace's Connect access is active (not paused, not disabled).
  2. The target sub-merchant exists and belongs to the calling marketplace — you can't borrow another marketplace's seller.
  3. The target sub-merchant is operable: KYC is approved and they're not currently suspended.

If any rule fails, the request is rejected before reaching the route handler.

Error codes

All errors come back with a JSON body shaped:

{
  "statusCode": 403,
  "message": "…",
  "errorCode": "…"
}
HTTPerrorCodeWhen
400ON_BEHALF_REQUIRED_FOR_MARKETPLACEMarketplace caller hit a merchant route without the header.
403ON_BEHALF_FORBIDDEN_CALLER_TYPEA non-marketplace caller (regular merchant) tried to use the header.
404ON_BEHALF_SUBMERCHANT_NOT_FOUNDThe id in the header doesn't correspond to a sub-merchant.
403ON_BEHALF_SUBMERCHANT_NOT_OWNEDThe sub-merchant exists but belongs to another marketplace.
403ON_BEHALF_SUBMERCHANT_NOT_OPERABLEKYC not approved, or the sub-merchant is currently suspended.
403ON_BEHALF_MARKETPLACE_PAUSEDThe calling marketplace's Connect access is currently paused.
403ON_BEHALF_CONNECT_DISABLEDConnect is disabled on the calling marketplace.

The same compliance kill-switches surface on the Connect-management routes too, with shorter codes: 403 MARKETPLACE_PAUSED and 403 CONNECT_DISABLED. See Suspension and Lifecycle.

Examples

Create a checkout payment for usr_seller_42

curl -X POST https://api.inflowpay.xyz/api/payment \
  -H "X-Inflow-Api-Key: $INFLOW_API_KEY" \
  -H "X-On-Behalf-Of: usr_seller_42" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "EUR",
    "successUrl": "https://shop.example.com/success",
    "cancelUrl":  "https://shop.example.com/cancel",
    "products": [
      { "name": "Hoodie", "price": 1500, "quantity": 1 }
    ]
  }'

Product price is in cents; the total is computed from products. The response returns { paymentId, purchaseUrl } — redirect the buyer to purchaseUrl (or embed it via the SDK iframe).

List a sub-merchant's customers

curl "https://api.inflowpay.xyz/api/customer?page=1&limit=20" \
  -H "X-Inflow-Api-Key: $INFLOW_API_KEY" \
  -H "X-On-Behalf-Of: usr_seller_42"

Trigger a payout for a sub-merchant

curl -X POST https://api.inflowpay.xyz/api/payout \
  -H "X-Inflow-Api-Key: $INFLOW_API_KEY" \
  -H "X-On-Behalf-Of: usr_seller_42" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId":     "bank_1234567890abcdef",
    "amountInCents": 50000,
    "currency":      "USDC",
    "network":       "base"
  }'

accountId references a payout destination (bank or wallet) that already belongs to the sub-merchant. currency is one of USDC or EURC. network is required only when the destination is a crypto wallet (avalanche, base, polygon, ethereum, solana, arbitrum).

Webhooks

Register webhooks on your marketplace API key (no X-On-Behalf-Of). Subscribe to connect.* event types — for example connect.payment.authorized and connect.payment.settled.

When a sub-merchant has activity, Inflow delivers a Connect event to your marketplace endpoint:

{
  "object": "connect_event",
  "eventType": "payment.authorized",
  "subMerchant": { "id": "usr_…", "merchantName": "Seller Alpha" },
  "data": { }
}

The Svix delivery uses the prefixed name in data[].eventType (e.g. connect.payment.authorized). Use subMerchant.id to route to the correct seller in your system.

See Webhooks overview and Verifying signatures.

Common pitfalls

  • Forgetting the header from a marketplace key. You'll get 400 ON_BEHALF_REQUIRED_FOR_MARKETPLACE. The header isn't optional once your account is a marketplace — there's no fallback.
  • Reusing the marketplace's own user id in the header. Marketplaces are not sub-merchants; the request will fail with 404 ON_BEHALF_SUBMERCHANT_NOT_FOUND.
  • Acting on a not-yet-approved sub-merchant. Even right after POST /api/connect/accounts, you'll get 403 ON_BEHALF_SUBMERCHANT_NOT_OPERABLE until KYC completes. Poll GET /api/connect/accounts/:subMerchantId/kyc/status until kycReady is true.
  • Acting on a suspended sub-merchant. Same 403 ON_BEHALF_SUBMERCHANT_NOT_OPERABLE. Resume them first via POST /api/connect/accounts/:subMerchantId/resume (see Suspension and Lifecycle).
  • Using Authorization: Bearer …. Inflow doesn't use bearer tokens for the public API — always send X-Inflow-Api-Key. See Authentication & API Keys.