Skip to main content
A payout account’s status says whether the seller can receive money, and a payout’s status says where one order’s payout stands. Neither is set by an operator. The provider decides, sends a webhook, and Mercur re-reads the provider’s view. Read this page to know when createOrderPayoutsWorkflow will pay an account, and what each webhook does.

Account statuses

An account’s state is the status field on PayoutAccount, typed by the PayoutAccountStatus enum from @mercurjs/types. It defaults to pending. The account also mirrors the provider’s flags: payouts_enabled, charges_enabled, details_submitted, disabled_reason, and a derived kyc_status (pending_submission, pending_approval, approved, refused). createOrderPayoutsWorkflow checks payouts_enabled and holds the payout until it’s true.

How the status is derived

Mercur doesn’t take a status from the webhook body. It asks the provider for the account with retrieveAccount and derives the status from what comes back:
  1. rejected when the provider says so, or disabled_reason starts with rejected
  2. pending while details_submitted is false
  3. active when payouts and charges are enabled and there’s no disabled_reason
  4. restricted in every other case
kyc_status follows the requirements: anything currently or past due is pending_submission, anything pending verification is pending_approval, a rejected.* disabled reason is refused, and an account with nothing outstanding is approved.

Webhooks refresh the account

The provider’s getWebhookActionAndData turns a raw webhook into an action and the provider’s account id. processPayoutForWebhookWorkflow finds the account by external_id and runs syncPayoutAccountWorkflow, which:
  1. Re-reads the account from the provider and stores the derived status, flags, and kyc_status
  2. Swaps the onboarding link for a provider dashboard login link once details are submitted
  3. Emits payout_account.updated with the account id
A Stripe account.updated webhook behind the process-payout-for-webhook workflow. The workflow maps account.activated to the activate-payout-account step and the Active status, account.restricted to Restricted, account.rejected to Rejected, and not_supported to no change. A Stripe account.updated webhook behind the process-payout-for-webhook workflow. The workflow maps account.activated to the activate-payout-account step and the Active status, account.restricted to Restricted, account.rejected to Rejected, and not_supported to no change. Every account.* action runs the same sync, so the provider’s current view always wins, whatever order the webhooks arrive in. You can run the same sync by hand with POST /admin/payout-accounts/:id/sync. The Stripe Connect provider handles Stripe’s account.updated event and reports account.rejected, account.activated, account.restricted, or account.updated, depending on the account it receives.

Payout statuses

Each payout and each reversal carries a PayoutStatus: status_reason holds a stable code that says why a payout or reversal is on_hold, failed, or aborted, from PayoutStatusReason in @mercurjs/types. The details behind it, such as an id, a status, or the provider’s error message, are in data.status_detail, for example Order is not ready yet, status is not_fulfilled. on_hold and failed payouts are re-decided by the next createOrderPayoutsWorkflow run, and operators can retry one, or a pending one, with POST /admin/payouts/:id/retry. A canceled order’s payout is aborted even when the seller has no usable account yet. paid, processing, and aborted payouts are never sent again. The Stripe Connect provider returns paid as soon as Stripe creates the transfer, so no webhook is needed to confirm it. Transfers are platform-account events and never reach the Connect endpoint.