Skip to content

Submerchant onboarding

Use the marketplace onboarding flow to connect a submerchant to your xMoney account and send them to a hosted onboarding experience at dashboard.xmoney.com.

Your marketplace creates a short-lived, signed redirect URL on its backend. The submerchant follows the URL, creates or connects an xMoney account, and provides their business and compliance information directly to xMoney. Your marketplace does not collect or send KYB documents through this flow.

How the flow works

  1. Your backend assigns a stable internal merchantId to the submerchant.
  2. Your backend creates and signs an xMoney Connect URL.
  3. Your application redirects the submerchant's browser to the signed URL.
  4. xMoney validates the signature and starts a secure browser session.
  5. The submerchant creates an account or signs in to an existing account.
  6. The submerchant completes the hosted business onboarding flow.
  7. Your backend polls the onboarding status using the same merchantId. xMoney does not send marketplace onboarding-status webhooks.
Hosted onboarding

The onboarding form and all KYB data collection are hosted by xMoney. Do not include business data, personal data, or documents in the redirect URL.

Before you start

Contact your xMoney representative to receive the credentials for your marketplace:

ValuePurpose
slugPublic identifier used in your Connect URL, for example acme-marketplace.
signingSecretShared secret used by your backend to sign redirect URLs.
keyIdUsername for onboarding-status polling.
pollingSecretPassword for onboarding-status polling.

Keep signingSecret and pollingSecret in a secret manager and use them only from your backend. Never expose them in browser code, mobile applications, client-side logs, or public repositories.

Confirm the production and test dashboard base URLs with your xMoney representative before integrating.

Create a signed onboarding URL

The production redirect URL has this format:

https://dashboard.xmoney.com/connect/{slug}?merchantId={MERCHANT_ID}&timestamp={ISO8601_UTC}&nonce={NONCE}&signature={SIGNATURE}

URL parameters

ParameterRequiredRules
slugYesYour xMoney-provisioned marketplace identifier. Lowercase letters, numbers, and hyphens only; 2–64 characters.
merchantIdYesYour stable internal identifier for the submerchant; maximum 191 characters.
timestampYesCurrent UTC time in ISO 8601 format. Use YYYY-MM-DDTHH:mm:ssZ. The URL is accepted for 15 minutes.
nonceYesA cryptographically random, one-time value. Use 8–128 letters, numbers, underscores, or hyphens.
signatureYesA base64url-encoded HMAC-SHA256 signature of the complete URL without the signature parameter.

Use a new timestamp and nonce every time you create a redirect, including when retrying a failed or expired redirect. Keep your server clock synchronized with UTC.

Sign the exact URL

First, construct the complete URL without signature. This unsigned URL is the payload:

https://dashboard.xmoney.com/connect/acme-marketplace?merchantId=merchant-123&timestamp=2026-09-03T10:15:00Z&nonce=I6d9jK2L0mN4pQ8R

Then compute:

signature = base64url(
  HMAC-SHA256(
    key = signingSecret,
    message = unsignedUrl
  )
)

Append the result as the final signature query parameter.

The signed value must match exactly

Query parameter order and encoding are part of the signature. Sign the exact URL string that you redirect the browser to, before appending signature. Do not parse, reorder, decode, or re-encode the URL after signing it.

Use the recommended query parameter order: merchantId, timestamp, then nonce.

Signing examples

These examples use only standard libraries.

const crypto = require('crypto');

function createSubmerchantOnboardingUrl({
  dashboardBaseUrl = 'https://dashboard.xmoney.com',
  slug,
  signingSecret,
  merchantId,
}) {
  const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
  const nonce = crypto.randomBytes(16).toString('base64url');

  const payloadUrl = new URL(`${dashboardBaseUrl}/connect/${slug}`);
  payloadUrl.searchParams.set('merchantId', merchantId);
  payloadUrl.searchParams.set('timestamp', timestamp);
  payloadUrl.searchParams.set('nonce', nonce);

  const payload = payloadUrl.toString();
  const signature = crypto
    .createHmac('sha256', signingSecret)
    .update(payload, 'utf8')
    .digest('base64url');

  return `${payload}&signature=${signature}`;
}

Call the relevant function on your backend and redirect the browser to the returned URL.

Redirect the submerchant

Return the signed URL from your backend and navigate the submerchant's browser to it using a full-page redirect.

Do not generate the signature in frontend code. A frontend application can request a newly signed URL from your backend immediately before redirecting:

const response = await fetch('/api/xmoney/onboarding-url', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ merchantId: 'merchant-123' }),
});

const { redirectUrl } = await response.json();
window.location.assign(redirectUrl);

A valid redirect starts an xMoney browser session for up to 24 hours. If the link is not validated within 15 minutes, or if its nonce was already used, generate a new signed URL.

What the submerchant completes

After xMoney validates the redirect, the submerchant can create a new xMoney account or connect an existing account. New accounts must verify their email address.

The hosted onboarding flow then collects:

  • legal business and contact information;
  • business activity, ownership, and legal-representative information;
  • website and processing information;
  • required company and identity documents;
  • payout bank-account information; and
  • final declarations and submission for review.

Referred submerchants must apply for FIAT processing. After submission, xMoney reviews the business and may request additional information before activation.

Poll onboarding status

Polling instead of webhooks

xMoney does not send webhook notifications when a referred submerchant's onboarding status changes. Poll this endpoint from your backend to retrieve the latest status.

Use the marketplace's internal merchantId to retrieve the current high-level onboarding status:

GET {partnersApiBaseUrl}/business-referrals/{merchantId}/onboarding-status
Authorization: Basic base64(keyId:pollingSecret)
Accept: application/json

For example:

curl --user "$XMONEY_KEY_ID:$XMONEY_POLLING_SECRET" \
  --header "Accept: application/json" \
  "$XMONEY_PARTNERS_API_URL/business-referrals/merchant-123/onboarding-status"

A successful response has this shape:

{
  "merchantId": "merchant-123",
  "xmoneyMerchantId": 7291,
  "status": "kyb_review_in_progress",
  "kybDecisionStatus": "pending",
  "contractStatus": null
}

The endpoint returns only referrals associated with the business represented by your credentials. A merchantId belonging to another marketplace is not accessible.

Response fields

FieldTypeDescription
merchantIdstringYour parent-scoped merchant identifier from the signed redirect.
xmoneyMerchantIdnumber or nullThe xMoney business identifier. It remains null until the submerchant submits a business application.
statusstringThe current overall onboarding lifecycle status.
kybDecisionStatusstring or nullThe current KYB decision: pending, success, failed, or null when no decision is available.
contractStatusstring or nullThe contract delivery status: sent, completed, or null. A declined contract is represented by status: contract_declined.

Status values

StatusMeaning
redirect_receivedThe signed redirect was validated, but the submerchant has not connected an account.
application_startedAn account is connected, but the FIAT business application has not been submitted.
ubo_kyc_pendingAt least one ultimate beneficial owner still needs to complete KYC.
kyb_review_in_progressxMoney is reviewing the submitted FIAT business application.
kyb_approvedKYB is approved and the application has reached the contract stage.
kyb_rejectedThe FIAT business application was rejected.
contract_sentThe contract was sent for signature.
contract_declinedThe contract was declined.
contract_signedThe contract was signed and account activation is pending.
account_activeThe FIAT onboarding is complete and the account is active.

Before the submerchant first opens and validates a signed redirect URL, xMoney has no record of the merchantId. Polling that ID returns 404 Not Found.

Treat the other common responses as follows:

HTTP statusMeaning
200 OKStatus returned successfully.
401 UnauthorizedThe polling credentials are missing or invalid.
404 Not FoundThe merchantId is unknown to this marketplace.

Continue polling while the onboarding process is progressing and stop after account_active. Coordinate any follow-up for kyb_rejected or contract_declined with xMoney. Use exponential backoff and avoid polling more often than the interval agreed with xMoney.

Authentication and IP restrictions

The polling endpoint uses HTTP Basic authentication:

  • Set the username to your keyId.
  • Set the password to your pollingSecret.
  • Send credentials only over HTTPS.

xMoney validates that the credential is active and verifies the polling secret securely. If an IP allowlist is configured for your credential, requests must also originate from an allowed IP address. Invalid credentials and disallowed IP addresses return 401 Unauthorized.

Security checklist

  • Create signed URLs only on your backend.
  • Store signingSecret and pollingSecret in a secret manager.
  • Generate a cryptographically random nonce for every redirect.
  • Never reuse a nonce, even for the same merchantId.
  • Generate the timestamp immediately before redirecting.
  • Keep the unsigned URL byte-for-byte identical after signing.
  • Use HTTPS in production.
  • Do not log secrets or complete signed URLs.
  • Do not place personal, business, or KYB data in query parameters.
  • Scope polling results by the merchantId stored in your own system.

Troubleshooting signed redirects

For security, invalid, expired, and reused redirects display a generic invalid-link response. Check the following before contacting xMoney:

ProblemWhat to check
Invalid signatureConfirm that both systems use the same signingSecret, HMAC-SHA256, UTF-8 input, and unpadded base64url output.
Invalid signatureConfirm that you signed the complete URL without signature and did not alter its parameter order or encoding afterward.
Expired linkGenerate the timestamp at redirect time and confirm that your server clock is synchronized.
Reused linkGenerate a fresh nonce and a new signature for every redirect attempt.
Unknown marketplaceConfirm that the URL path uses the exact lowercase slug provisioned by xMoney.
Missing merchantInclude a non-empty merchantId that is no longer than 191 characters.

When requesting support, share the assigned slug, environment, your merchantId, and the approximate UTC time of the failure. Do not send your signing or polling secrets.