Skip to content

Drive the merchant's PayPal onboarding

Call POST /api/channel/onboarding/start with the channel key and the merchant’s X-Merchant-Id. Store the returned action_url and open it for that merchant. Re-show the stored URL when the merchant returns to the onboarding step instead of starting a new successful attempt unnecessarily.

start-onboarding.sh
curl --request POST \
--url https://staging.api.maxanapay.com/api/channel/onboarding/start \
--header 'Authorization: Bearer sk_test_replace_me' \
--header 'X-Merchant-Id: b3f1c8de-7a20-4c55-9e64-1d8f2a6b40c7' \
--header 'Content-Type: application/json' \
--data @start-onboarding.request.json

The whole response is action_url, and the whole request body is optional: sending {} is the same as sending {"reconnect": false}. The referral token inside the URL is minted for this attempt and this merchant; it is not a credential you store beyond the attempt.

In sandbox, ask us for the seller account the merchant signs in with, and for the personal account the buyer will pay with later; see Sandbox access is issued by Maxana. Both are PayPal sandbox accounts, and they are not interchangeable.

PayPal’s seller-onboarding consent screen identifies maxanapay as the partner requesting access, and asks the merchant to connect an account so it can accept payments on that partner’s platform. It does not show the channel as the PayPal partner. Tell the merchant that name before opening action_url: the point of the warning is that the screen matches what they were told, so a name they were not expecting is worse than no warning at all.

This exposure is limited to seller onboarding. When a buyer later opens the PayPal payment window, PayPal uses the merchant’s own business name. The buyer checkout is therefore merchant-branded; the seller-consent screen is the one place where the merchant sees the partner.

That name is production’s. The name comes from the PayPal application the referral was minted by, and sandbox runs a different application from production, so the sandbox screen shows a different name — reading it there and repeating it to a production merchant is how the wrong name gets announced. Take the name above for what you tell a merchant in production, and expect the screen you see while integrating in sandbox not to match it.

A merchant may finish at PayPal without returning to the channel’s browser URL, so the return is navigation only, not a completion signal. Where the merchant actually stands is the status read, below.

Use {"reconnect": true} when a connected merchant must revisit PayPal to grant missing access or connect another PayPal account. A successful call made again is a new onboarding attempt, so keep the action URL already returned for the current attempt.

A 409 means either that onboarding is already complete or that the merchant’s PayPal link state changed while the referral was being minted. A state-change conflict returns no action_url; read the current onboarding status and retry only after any reported account conflict or administrative hold is resolved.

GET /api/channel/onboarding/status reports the merchant’s latest known state.

read-onboarding-status.sh
curl --request GET \
--url https://staging.api.maxanapay.com/api/channel/onboarding/status \
--header 'Authorization: Bearer sk_test_replace_me' \
--header 'X-Merchant-Id: b3f1c8de-7a20-4c55-9e64-1d8f2a6b40c7'

The response above is a merchant that has finished and can charge. Read paypal_connection_status first — connected is the one value that means a buyer can be charged — and the rest of this page for what each other field answers when the merchant cannot.

The operation declares 13 fields, and answers with 5 of them every time:

  • merchant_id
  • onboarding_status
  • paypal_connection_status
  • payments_enabled
  • checkout_enabled

The other 8 are optional, and which of them you receive depends on the deployment your credential was issued for. The three capability fields under What PayPal is still deciding are the ones to expect missing: a deployment that does not report them leaves the keys out of the response rather than answering null, so a reader that indexes into them straight from the body fails on a field that is absent rather than empty. Read every field outside the list above through a default.

paypal_primary_currency is the one that reports the currency to open sessions in: see which of them a merchant can be paid in and open each session in it.

The read is not a live call to PayPal on every request, and it also keeps answering from the last known state when PayPal cannot be reached, so a change made at PayPal moments ago may not be in the answer yet. Treat merchant.onboarding_updated as the signal that something moved, and a single unchanged read as no evidence that nothing did.

It is not a passive read either, and that cuts both ways. When its snapshot is stale, the call refreshes it from PayPal and stores what comes back — so the state you are shown can be the state your own call just produced. A merchant reporting paypal_scope_refresh_required can report connected on the very next call, because reading is what fetched the grants that were missing. The same refresh is what raises merchant.onboarding_updated, so one of your polls can be why an event arrives.

A channel that polls is therefore doing something, and a channel that avoids polling to be gentle is preventing something. Call it where a merchant is waiting — the screen that tells them PayPal has not finished is exactly where this belongs — and stay inside the operation’s budget of

60 per 1 minute. A merchant nobody polls is not stranded either: state moves without you, which is what merchant.onboarding_updated is for.

Field Answers
merchant_id Which merchant this is.
onboarding_status How far the merchant got.
paypal_connection_status Whether a PayPal account is attached, and whether it is usable.
paypal_status_reason What is holding the merchant up, when something is.
paypal_status_reason_fresh Whether that reason reflects PayPal’s current answer or a stale one.
payments_enabled Whether everything needed to charge has come together.
checkout_enabled Whether the merchant can charge a buyer right now.
paypal_missing_scopes Which access the merchant did not grant.
paypal_scope_feature_status Which payment features are available for this merchant.
paypal_acdc_status What PayPal is still deciding about advanced card checkout.
paypal_apple_pay_status What PayPal is still deciding about Apple Pay.
paypal_google_pay_status What PayPal is still deciding about Google Pay.
paypal_primary_currency The account’s primary settlement currency; open sessions in it.

paypal_connection_status is the field that answers this, and connected is the only value that means yes. It is the one value that requires all three of PayPal reporting the seller ready, every access the platform needs granted, and no administrative hold — which is why it is the field to gate a buyer on.

checkout_enabled answers something narrower: whether Maxana will let a payment be created for this merchant. One it reports as false is refused with 403 when a PayPal payment is created, so there is nothing to gain by trying. One it reports as true has cleared that gate and nothing beyond it.

It answers for the PayPal rail, which is the rail this page is about. A merchant Maxana routes to another payment provider is not gated by it, so do not read a false here as that merchant being unable to take any payment at all.

payments_enabled is not a PayPal-only verdict, so do not read a false there as a problem at PayPal. It is false unless PayPal reports the seller ready, the merchant granted every access the platform needs, and no administrative hold is in place. paypal_status_reason is what says which of those it was.

The merchant that passes every check and fails at capture

Section titled “The merchant that passes every check and fails at capture”

The two booleans can disagree, and the case where they do is the expensive one. A seller whose PayPal account can receive money and holds a checkout product, but whose email address PayPal has not confirmed, answers like this:

Field Value
paypal_connection_status linked_pending
paypal_status_reason email_unverified
payments_enabled false
checkout_enabled true

checkout_enabled is true there, and everything a channel can do works: the session opens, the buyer signs in at PayPal, the buyer approves. The capture is then refused, because PayPal will not be paid into an account whose email it has not confirmed. That refusal arrives as a 400 on the capture call, naming the merchant’s PayPal account as unverified — after the buyer has authorized the payment and believes they have paid.

So checkout_enabled: true is not permission to charge, and a merchant can pass every check on this page and still fail at capture. Gate the buyer on paypal_connection_status: "connected", which this merchant is not. In sandbox, which PayPal account the merchant connected decides whether it can ever reach that state.

The events say the same thing, which is what a channel reconciles from rather than the buyer’s browser. The sequence you observe is payment.approved, then payment.failed naming the refusal in reason. The payment reaches a terminal state and stops there.

Not every failed attempt raises that event, so its absence is not evidence a payment succeeded. A decline the buyer can act on — another funding source, another approval — is reported on the buyer’s page and raises no payment.failed, because the same checkout can still be paid; and an attempt whose outcome could not be read raises nothing until it resolves. Reconcile those rather than waiting on an event.

payment.failed ends the payment attempt, not the checkout. The same session can carry another attempt, and that attempt is a new transaction with events of its own, so do not read the event as the end of the order.

The reason stays readable after the delivery, on the transaction itself, as failure_code and failure_message. A delivery you missed is not the only place it was ever stated.

Nothing the buyer does clears a refusal that names the merchant’s PayPal account, though: until paypal_status_reason changes, every attempt on that merchant ends the same way.

paypal_status_reason is null when nothing blocking has been recorded. On a merchant that is not yet connected, null means nothing has been reported yet; it is not a statement that the merchant is ready. Nine values are possible.

Read paypal_status_reason_fresh alongside it. This read refreshes the merchant from PayPal before answering, but when a recent refresh is still cached, or a provider error has forced a short back-off, the reason served is the last one persisted rather than one derived just now. paypal_status_reason_fresh tells the two apart: true means the reason — null included — reflects PayPal’s current answer, so a null reads “connected, nothing to relay”; false means the reason is not currently known, so a null must be read as “unknown, poll again”, not as “fine”. Gate on paypal_connection_status in either case; the marker only says how much to trust a null reason on its own.

paypal_status_reason What it means What to do
null Nothing blocking is recorded — but only trust this when paypal_status_reason_fresh is true. Read paypal_connection_status before offering checkout; null here is not the same as ready. If paypal_status_reason_fresh is false, treat it as unknown and poll again.
email_unverified PayPal has not confirmed the email address on the seller’s account. Ask the merchant to confirm that address from their PayPal account — in sandbox, see below first.
payments_not_receivable PayPal will not let the account receive money yet. The merchant resolves this inside their PayPal account.
missing_checkout_capability PayPal has not granted the account a checkout product. Read the status again later; nothing on the channel side changes it.
missing_paypal_scopes The merchant linked an account but withheld access the platform needs. Send the merchant back with {"reconnect": true} and have them accept every permission. paypal_missing_scopes names what is missing.
paypal_scope_refresh_required An account is attached but the access it granted has not been read back yet. Read the status again. If it persists, send the merchant back with {"reconnect": true}.
paypal_account_already_linked The PayPal account the merchant chose is already attached to a different merchant. Have the merchant onboard a different PayPal account, or contact Maxana to release the existing link.
admin_payment_hold Maxana has stopped payments for this merchant. Contact Maxana. Nothing the merchant does at PayPal clears it.
revoked The merchant is no longer connected and PayPal reports no problem of its own: consent was withdrawn, or the disconnection was asked for on Maxana’s side. Start onboarding again; a successful reconnection clears it.

A PayPal account can be attached to exactly one Maxana merchant, and that is true across the whole platform rather than within your channel. The merchant holding it may be another of yours or a merchant of a channel you cannot see; the answer is the same either way and nothing here tells you which.

Only the first remedy on that row is yours to act on: have the merchant onboard a different PayPal account. Releasing the existing link is a support request to Maxana, not an operation this API exposes, so treat it as something to start early rather than something to wait for.

Merchants choose their own PayPal accounts, so expect this the first time two of them choose the same one.

The sandbox seller that cannot confirm its email

Section titled “The sandbox seller that cannot confirm its email”

email_unverified has a remedy in production and none in sandbox. A sandbox seller created inside PayPal’s own sign-up, rather than in the PayPal Developer Dashboard, ends onboarding with its address unconfirmed and offers the merchant no way to confirm it: the account’s own email settings show it unconfirmed with nothing to click. Nothing you or the merchant can do from the channel clears it, and the capture stays refused.

Start again with a business account created in the PayPal Developer Dashboard, which arrives confirmed. In production the row’s remedy stands as written.

Read the section above before this pair. Where a reason is recorded, that is the field saying what happened, and the pair below can read as though the merchant never came back from PayPal when they did.

onboarding_status alone does not separate a merchant that is still filling in PayPal’s form from one that finished and is waiting on PayPal. Read it together with paypal_connection_status, which is what says whether a PayPal account is attached at all.

onboarding_status Meaning
pending The merchant exists and nothing has been started. No one has been sent to PayPal.
in_progress Onboarding was started. Whether the merchant reached the end of PayPal’s form is in paypal_connection_status.
completed A PayPal account is attached and everything Maxana requires has been granted.
revoked PayPal readiness for this merchant lapsed, or its access was withdrawn. paypal_status_reason says which, and only withdrawn access is fixed by starting onboarding again.
paypal_connection_status Meaning
not_linked No PayPal account is attached. With in_progress and no reason recorded, onboarding was started and no account has come back from PayPal. With a reason recorded, one came back and the link was refused.
linked_pending The merchant finished at PayPal and an account is attached, but PayPal has not granted everything the merchant needs. Read paypal_status_reason.
connected An account is attached and usable.
restricted An account may be attached, but payments are stopped. Read paypal_status_reason.

So in_progress with not_linked and no reason recorded is a merchant that has not come back from PayPal, and in_progress with linked_pending is one that has and is now waiting on PayPal. The two need different messages, which is why the pair is worth reading rather than the status alone. Neither says whether the merchant ever opened the link: in_progress is recorded when the channel starts onboarding, so a merchant that received the URL and ignored it looks exactly like one that opened it and gave up.

There is one case the pair does not just miss but inverts. A merchant whose chosen PayPal account is already attached to another Maxana merchant reads in_progress and not_linked — indistinguishable from a merchant who never went — while PayPal has told that merchant they are connected and ready to receive payments. Nothing was refused at PayPal; the link was refused here. paypal_status_reason is paypal_account_already_linked, and it is the only field that separates the two.

Which payment features has PayPal granted?

Section titled “Which payment features has PayPal granted?”

paypal_missing_scopes lists the access the merchant did not grant, drawn from PAYMENT, REFUND, and PARTNER_FEE. An empty list is not proof that everything was granted: it is also what a merchant with no attached PayPal account returns, because there is nothing yet to evaluate. Pair it with checkout_enabled.

paypal_primary_currency is the merchant’s primary PayPal settlement currency — USD, BRL, and so on — read from PayPal once the account is linked, and null until then. Maxana opens a PayPal session only in this currency (its own restriction, not a claim that PayPal could not process another), so a session in a different currency is refused at creation; open each session in this currency rather than asking the merchant which one their account takes. While it is still null, that check cannot run, so treat an unknown settlement currency as not yet proven for a second currency; see which of them a merchant can be paid in.

paypal_scope_feature_status maps each payment feature to whether it is available for this merchant. Six keys are always present.

Key Feature
checkout_platform_fee Charging a buyer through PayPal with the platform fee applied.
disputes Reading and responding to the merchant’s disputes.
transaction_search Searching the merchant’s PayPal transactions.
reporting_reconciliation Reconciling the merchant’s settlement against PayPal.
merchant_info Reading the merchant’s PayPal account details.
advanced_card_checkout Collecting card details in fields hosted on the merchant’s own page.

A connected merchant commonly has advanced_card_checkout set to false while every other key is true. That merchant can take PayPal payments today and cannot present card fields; PayPal grants advanced card processing separately and on its own schedule. Read this key before offering card fields rather than letting the merchant find out at checkout, and paypal_acdc_status below for what PayPal is doing about it.

These keys are necessary, not sufficient. Venmo and Pay Later additionally require a merchant in the United States, and Pay Later is withdrawn again from a merchant that has turned it off, so checkout_platform_fee on its own is not a reason to advertise either one.

PayPal underwrites some payment capabilities separately from the account itself — advanced card checkout, Apple Pay and Google Pay — and the last three fields report where each of them stands:

  • paypal_acdc_status
  • paypal_apple_pay_status
  • paypal_google_pay_status

Each is null until PayPal has answered for that merchant, and then carries one of five values. The middle column lists the PayPal statuses each value is reached from — a capability’s own status, or the vetting_status of the product behind it. It is a vocabulary, not a rule: seeing one of those statuses is not on its own enough to produce the value beside it.

ValuePayPal reportsWhat it means, and what to do
unavailableSUBSCRIBED_WITH_LIMITMaxana has no usable answer for this capability. Usually PayPal was never asked for it, or it is not offered where the merchant trades. It also covers the statuses named alongside, where PayPal did answer but not in terms that reduce to one value. Do not rely on the method. If the merchant says it works but keeps hitting a ceiling, that is the restricted case, and the limit is theirs to take up with PayPal.
in_reviewIN_REVIEW, PENDING, APPROVEDPayPal has the capability in hand and has asked the merchant for nothing: a review is running, or it is approved but not usable yet. Wait.
need_more_dataNEED_MORE_DATA, NEED_DATAPayPal needs supplementary information from the merchant before it can finish. Tell them today; nothing moves until they supply it at PayPal.
deniedDENIED, DENY, DECLINED, SUSPENDED, REVOKED, INACTIVEPayPal will not let the merchant use it — refused, or withdrawn after being granted. Waiting resolves neither. PayPal asks a refused merchant to apply again; a withdrawn capability has to be taken up with PayPal.
activeACTIVE, SUBSCRIBEDPayPal granted it. Offer the method at checkout.

active is the one value that needs both halves to agree: the product vetted as SUBSCRIBED (or ACTIVE) and the capability itself ACTIVE. A subscribed product whose capability PayPal has not turned on yet is not active. The other four are reached from either half on its own.

unavailable also absorbs the states below, where PayPal answered but not in terms that reduce to one value:

  • SUBSCRIBED_WITH_LIMIT — PayPal finished provisioning under a restriction, such as a transaction limit. The restriction itself lives in a limitations array Maxana reads nowhere, so calling this granted would advertise a capability whose ceiling the caller cannot see.

in_review and need_more_data both leave the capability unusable, and only one of them is waiting on the merchant. Read them before answering a merchant who asks why a payment method has not appeared.

One caveat on the two wallets. PayPal reports advanced card checkout as a product, where it states separately that a review is in progress and that a merchant owes it information. Apple Pay and Google Pay it reports only as capabilities, and its guidance for those groups “reviewing” and “needs data” together. So in_review on advanced card checkout means PayPal has asked the merchant for nothing; on a wallet it is the weaker statement that PayPal has not said what it is waiting for. If a wallet sits in in_review and does not move, have the merchant check their PayPal account for an open request.

The channel cannot replace the partner identity on PayPal’s seller-consent screen. There is no channel API operation that marks onboarding complete, disconnects a channel merchant, or chooses PayPal capabilities. PayPal controls eligibility, and the status contract reports the result.

The reason a merchant is not connected at all is reported at the account level, where the same distinction is not yet drawn: a merchant PayPal is still reviewing and a merchant PayPal has asked for documents both read as payments_not_receivable or missing_checkout_capability, the same values several other causes produce. The per-capability values above are the only place the contract separates the two today. Where it does not, the notices on the merchant’s own PayPal account are what says why.

Continue to Register group webhooks.