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.com/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.com/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.com/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":      "EURC"
  }'

accountId references a payout destination (bank account or wallet) that already belongs to the sub-merchant. currency is the settlement currency to draw from (EURC or USDC). For crypto wallet destinations, also pass network (base, polygon, ethereum, solana, arbitrum, avalanche).

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.

Did this page help you?