Skip to content

Onboarding

The lifecycle of a connection, and what freezes the moment a merchant accepts it.

A connection carries two statuses that move independently. status says whether the merchant has accepted, payment_status says whether the connection may move money. A live connection can be active and still refuse every payment, which is the single most common surprise in this section.

The lifecycle

statusWhat it meansHow it got there
pendingCreated, nobody has acceptedPOST /accounts
activeA merchant signed in and authorised itThey opened the authorisation link
cancelledEnded, by youDELETE /accounts/{id}
expiredThe invitation ran out before anyone acceptedTime

There is no restricted and no intermediate state. A cancelled connection reads cancelled, one with a merchant attached reads active, an invitation nobody accepted before its date reads expired, and everything else reads pending. expires_at is the invitation's date: it is cleared when a merchant accepts, and an accepted connection never expires.

Creating a connection mints a single-use authorisation token and hands it back inside a URL.

What comes back on an unclaimed connection
{
"id": "acc_7Yh2MpL4tRb3nP8sZcXv",
"reference": "shop-amina",
"status": "pending",
"payment_status": "inactive",
"authorization_url": "https://sync.wajub.com/oauth/v2/authorize?access_token=…"
}

The merchant opens it, signs in to their own Wajub account, sees the capabilities you asked for, and accepts. Your platform never handles their credentials and never creates anything on their side.

An invitation does not stay open forever: it expires 30 days after its link was last minted, by creating the connection, by POST /accounts/{id}/token or by Resend invitation in the Dashboard, each of which restarts the 30 days. The last two revoke the links already sent. Three days before that, a reminder goes to the merchant's address if you gave one, with email on POST /accounts or PUT /accounts/{id}; it is kept in profile.email. The reminder carries a link of its own, and the link you already sent keeps working. Once the invitation expires, every link in the merchant's inbox stops working. A Relay connection has no invitation, no authorization link (authorization_url is null) and never expires.

You can hold up to 50 open invitations per environment: connections created and not yet accepted, cancelled or expired. Relay connections do not count, since no merchant ever accepts them. Past the cap, POST /accounts answers 429 with You have reached the maximum of 50 pending invitations. Cancel the invitations you no longer need, or let them expire.

You see authorization_url once, in the answer to the call that minted it: creating the subaccount, or regenerating its token. Reading the subaccount afterwards never returns it, claimed or not. Keep it when you receive it; if you lose it, mint a new one. Once the connection is claimed, slave appears in the response, carrying the merchant's business name and email.

A connection is claimed once, and the checks run at the moment of the claim, not when the merchant opened the link:

  • a connection you cancelled stays cancelled, and the link cannot revive it. POST /accounts/{id}/token can: on a cancelled invitation nobody claimed, it mints a new link and puts the connection back to pending, with 30 days again;
  • a connection another merchant already claimed cannot be taken over;
  • an expired invitation is refused;
  • in live mode, the merchant cannot claim it before filing their KYC. Filing is enough to connect; verification is what releases payments (below);
  • the link can carry a redirect_uri, but only one sharing an origin with the callback registered on the connection. Any other host is refused, and a connection with no callback accepts none.

A merchant who submits twice lands on their own finished connection, not on an error.

Back on your page

Before you send the link, you can add &state=… (a value of yours, such as a random token tied to the vendor's session) and &redirect_uri=… to it. Once the merchant has accepted, they are sent to that redirect_uri, or to the connection's callback when the link carries none, with these query parameters:

ParameterWhat it carries
accountThe connection's id (acc_…), the one every call and every account.* event uses
stateThe state you put on the authorisation link, returned as it was. Check it against the one you stored, to know the visit is the answer to your own link
statusconnected
The return, for a link opened with state=8f3a
https://example.com/connected?account=acc_7Yh2MpL4tRb3nP8sZcXv&state=8f3a&status=connected

The callback must be an HTTPS address: POST and PUT /accounts answer 422 otherwise, except http://localhost and http://127.0.0.1 with a sandbox key.

The return is a convenience for the merchant's browser, not a proof: anyone can open that URL. Confirm the connection with GET /accounts/{account} or wait for account.connected on your webhook endpoint before you act on it.

Then payments have to be switched on

Acceptance is not always permission to charge. payment_status is set the moment the merchant accepts.

The merchant acceptspayment_status
In sandboxactive straight away
In live, with their verification already clearedactive straight away, provided a lite connection also has the Wajub rail open
In live, with their verification still under reviewinactive

An inactive connection turns active on its own when the merchant's verification clears, and, on a lite connection, once the Wajub rail is granted to them. Wajub then sends account.payment_activated.

payment_statusEffect on requests carrying X-Sync
inactiveCalls that move money refused with 403; reads run
activeEverything the capabilities allow runs
suspendedCalls that move money refused with 403; reads run

What is refused while payment_status is not active: creating, activating, updating or charging a payment, creating or changing a payment link, a refund, a transfer or a batch, adding, changing or verifying a beneficiary or recording what a seller owes back, and accepting or closing a dispute. What still works: every GET, so you can follow what the connection did while the merchant's verification is under review, plus cancelling a payment, deleting a payment link or a beneficiary, and the identity lookups (/identity/resolve, /identity/validate).

Suspension is yours to use afterwards. Setting payment_status to suspended stops inbound payments and outbound transfers together, deliberately: a connection frozen for a failed compliance recheck should not be able to drain its balance while it is unable to earn.

The switch only lifts a suspension you applied yourself. Setting payment_status back to active answers 409 with error_code: merchant_not_verified while the merchant's verification is pending or was rejected: those connections come back on their own once verification clears.

A Lite seller's own portal

A lite seller has no merchant dashboard, which is the point of the mode. Their onboarding ends on the Wajub rail application rather than on a dashboard, and afterwards they use a portal of their own, carrying your branding.

They doWhere
Sign inA one-time code sent to the shop's email address, with no password and a short session
See their sales and payoutsOnly those carrying this connection: never their other business, never another platform's
Apply for the Wajub railHosted, during onboarding; the connection cannot charge until it is approved
Declare where their money goesThe portal's payout account page

What the merchant has to agree to again

The moment a merchant accepts, two of the four editable fields stop being yours alone.

FieldAfter acceptance
capabilities202 Accepted: proposed to the merchant, returned in pending_changes
pricing202 Accepted: proposed to the merchant, returned in pending_changes
callbackStill editable outright
payment_statusStill editable outright, and only editable from here

The asymmetry is deliberate. What the merchant agreed to on the authorisation screen cannot be changed behind their back, so a wider connection or a higher commission is a proposal they accept or decline in their own dashboard, and the connection keeps running on its current terms in the meantime. Suspension stays yours, with no proposal needed.

On their side, the merchant finds every platform they are connected to under Settings, Connected apps: the capabilities they granted, the commission they agreed to, whether payments are on, and any proposal waiting for them. Accepting or declining a proposal there sends you account.change_accepted or account.change_declined. The same page lets them disconnect from your platform, which ends the connection exactly as your own DELETE would.

Ending a connection

DELETE /accounts/{id} revokes the tokens, sets status to cancelled, and fires account.deauthorized. If the merchant had accepted, payment_status drops to inactive at the same time and the response reads Account disconnected; on an unclaimed invitation it reads Account cancelled.

Disconnect a merchant
curl -X DELETE https://api.wajub.com/accounts/acc_7Yh2MpL4tRb3nP8sZcXv \
  -H "Authorization: sk.xxxxxxxxxxxxxxxx…"

Disconnecting does not touch anything that already happened. The merchant keeps their account, their payments and their balance, and the commissions you already earned stay earned. What stops is your ability to act on their behalf.

The events to listen for

A connection's life is told by the events below, on your platform's endpoints. Creating a connection sends account.created, with the account but not its link: authorization_url is only in the answer to POST /accounts. The event catalogue describes every one of them.

EventSent when
account.createdYou create it with POST /accounts
account.onboarding_startedThe merchant opens the authorisation link
account.onboarding_email_verifiedThey confirm their email address
account.onboarding_account_selectedThey pick the Wajub account to connect
account.onboarding_kyc_submittedThey file their KYC during the onboarding
account.connectedThey accept the connection
account.payment_activatedIt becomes able to charge
account.payment_suspendedIt is suspended, by you or after a failed verification
account.change_requestedYou propose new capabilities or pricing
account.change_acceptedThe merchant accepts a proposal
account.change_declinedThe merchant declines a proposal
account.updatedIt changes: through PUT /accounts/{id} (not a proposal alone, not a call that changes nothing), when POST /accounts/{id}/token puts a cancelled invitation back to pending, or when Wajub sends a Lite merchant's payout (last_payout_at)
account.commission_receivable_recordedA commission is owed on a sale collected outside Wajub
account.commission_receivable_collectedThat commission is collected
account.deauthorizedIt is disconnected or cancelled

On top of those, every payment, refund and transfer made on a connected account, and every dispute on one of its payments, fires its normal webhook to the merchant and a copy to your platform. You do not need to poll a connection to know what it is doing.

What did you think of this content?