Subaccounts
Create and manage merchant subaccounts for marketplaces and platforms.
Subaccounts let a platform connect merchants and act on their behalf, as part of Sync.
A payment made through a connection belongs to the merchant; the platform's commission, set by the
connection's pricing, is credited to the platform, or owed to it when the merchant collects on
their own providers (Split payments).
private key required
Creating and managing subaccounts requires the private key (sk...).
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /accounts | Create |
GET | /accounts/{id} | Retrieve |
GET | /accounts | List |
PUT | /accounts/{id} | Update |
DELETE | /accounts/{id} | Delete |
POST | /accounts/{id}/token | Regenerate the OAuth authorization token |
POST | /accounts/{id}/payouts | Pay a Lite merchant now (live only) |
GET | /accounts/{id}/commission-receivables | What merchants collecting on their own contracts owe you |
The path is /accounts
This resource is exposed under /accounts, not /subaccounts, subaccounts here refers to the
product concept (merchants connected under your platform), matching the "Sync" product name.
The Account (subaccount) object
idstringoptionalreferencestring?optionalsandboxbooleanoptionaltrue for a test-mode sub-account.capabilitiesstring[]optionalprofileobjectoptionalprofile.email, the merchant address you give with email on POST or PUT /accounts, where the invitation reminder is sent.metadataobjectoptionalmetadata on POST /accounts. PUT /accounts/{id} merges what you send into it, and a key sent as null is removed. {} when there is none.callbackstring?optionalhttp://localhost and http://127.0.0.1 are accepted with a sandbox key, anything else answers 422. It also registers the origin: a redirect_uri on the authorization link is only honoured when it shares that origin, and refused otherwise. Without a callback, no redirect_uri is accepted.account_typeenumoptionaldirect (the merchant runs a full Wajub account), lite (a hosted portal, always collecting through Wajub) or relay (no merchant account: you collect and Wajub relays the payouts). Defaults to direct, and cannot be changed afterwards.commission_refund_policyenumoptionalprorata (default, in proportion to the refund), full (all of it, on the first refund) or none. Settable at creation, and editable only while the connection is pending.statusenumoptionalpending, active, expired or cancelled: see [the lifecycle](/sync/onboarding#the-lifecycle). GET /accounts?status= filters on this same value.payment_statusenumoptionalactive, inactive (the merchant's verification is still under review) or suspended (by you, or by Wajub when the verification was rejected): whether calls that move money run.payout_scheduleenumoptionallite accounts only: manual, daily, weekly or monthly, how often Wajub pays the merchant.payout_min_amountnumber?optionallite accounts only: nothing is paid out below this, in the merchant's currency. null means the default, 1 000.last_payout_atdatetime?optionallite accounts only: when the merchant was last paid.pending_changesobject[]optional{ id, type, proposed, current, requested_at }.authorization_urlstring?optionalrelay connection has none: it is always null there, and regenerating it answers 422.masterobject?optional{ name }.connected_merchantobject?optional{ name, email }.pricingobject?optional{ percentage_fee, fixed_fee, min_fee, max_fee, currency }.expires_atdatetime?optionalnull once a merchant has accepted, since an accepted subaccount never expires, and always null on a relay connection, which has no invitation.created_atdatetimeoptionalupdated_atdatetimeoptionalCreating a subaccount
The pricing object accepts percentage_fee, fixed_fee, max_fee, min_fee (all optional,
numeric), and currency (required if any fixed/min/max fee is set). email is the merchant's
address: Wajub sends the invitation reminder there three days before the invitation expires. It is
kept in profile.email, and PUT /accounts/{id} changes it. metadata is yours, returned on the
object. A reference already used by one of your connections of the environment, cancelled ones
included, answers 422. Past 50 open invitations, the call answers 429
(the cap):
curl https://api.wajub.com/accounts \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-d '{
"reference": "vendor-482",
"capabilities": ["payments"],
"email": "contact@vendor-482.example",
"pricing": { "percentage_fee": 10, "currency": "XAF" }
}'Acting on behalf of a subaccount - the X-Sync header
Once a subaccount is active, the platform can act as it by sending its id in the
X-Sync header alongside the platform's own API key. When present and valid, the request runs
against the subaccount instead of the platform, resources are created under the
subaccount, and its own pricing is applied automatically.
What the platform reaches is what this connection brought: its payments and their refunds and
disputes, the customers who bought through it or that it created or named by email or phone
(without the merchant's notes, block reason and metadata, and never deleted through it), the links,
invoices and beneficiaries it created, the payouts it sent and the events about all of these. The merchant's own sales, links, invoices and
payees, and what other platforms brought, are out of scope whatever capabilities were granted, and
answer 404. Account types
details each resource.
X-Syncheaderoptionalid (e.g. acc_7Yh2Mp), not its reference. Sent alongside the platform's Authorization key.curl https://api.wajub.com/payments \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
-H "X-Sync: acc_7Yh2Mp" \
-d '{ "amount": 10000, "currency": "XAF", "email": "client@example.com" }'On a live connection, PUT /accounts/{id} does not change capabilities or pricing outright: it
answers 202 Accepted with the proposal in pending_changes, and the merchant accepts or declines
it in their own dashboard. payment_status and callback still apply immediately.
Every resource states the capability it takes, and a resource that states none cannot be reached
with X-Sync at all. That is the case for /accounts/*, which you always manage with your own
key, and for what belongs to the merchant's whole account rather than to a connection: the balance,
webhook endpoints, Shield, Tax and wajub listen.
| If the request touches | Required capability |
|---|---|
/payments, /links | payments |
/invoices | invoices |
/customers, /customers/{id}/tax_ids | customers |
/refunds | refunds |
GET /payments/{id}/refunds, POST /payments/{id}/close with outcome: refund | payments and refunds |
/disputes | disputes |
/transfers, /beneficiaries, /identity | withdrawals |
/, /providers, /events | read |
/channels, /countries, /currencies | none, catalogues |
/balance, /webhooks, /shield, /tax | not available with X-Sync |
/accounts | not available with X-Sync |
When a call with `X-Sync` is refused
Only the platform that created a subaccount can act on its behalf, and only in the key's
environment: another platform's subaccount, or a sandbox subaccount called with a live key (or the
reverse), cannot be reached: it answers 404 Sync account not found., like an id that does
not exist. A subaccount that isn't active (still pending, or
cancelled/expired), that lacks the capability the resource requires, or that is called on a
resource not available with X-Sync (see the last rows above) answers 403. For payments,
withdrawals and refunds, a call that moves money is also refused with 403 while the
subaccount's payment_status isn't active: collection, payouts and refunds are suspended
together, even if capabilities still lists them. Reads (GET) still run, as do cancelling a
payment, deleting a link or a beneficiary and the /identity lookups.
What is refused