HomeDocumentationXRPay Connect
Platform integration

Let each business connect XRPay without sharing API keys

Add a Connect XRPay button to your software platform. Each customer business authorizes its own account; your server creates checkouts for that business, and a signed webhook returns the payment result with your order reference.

What the connected business keeps private

Your platform never receives the business owner's XRPay password, XRPay API secret, or wallet key. It receives a scoped token tied to one approved connection.

The connected-business flow

Use the same flow whether your customers run stores, hotels, clinics, schools, restaurants, field-service teams, creator businesses, or marketplace accounts. Each business approves a separate connection.

1

A business owner chooses Connect XRPay

Your platform creates state and PKCE values, then redirects the owner to XRPay.

2

XRPay shows the requested access

The owner signs in or registers and reviews each scope. An XRPL address is optional unless XRP or RLUSD is needed.

3

Your server receives a single-use code

XRPay returns the code to the exact registered callback URL. The code expires after five minutes.

4

Your server stores connected-account tokens

Exchange the code with the client secret and PKCE verifier. The business never copies an XRPay API key.

5

Your platform creates a hosted checkout

Send the order amount, currency, reference, line items, and one stable idempotency key.

6

The customer chooses an available payment method

XRPay shows the intersection of methods requested by the platform and ready merchant-owned rails.

7

A signed event confirms the payment

Verify the raw-body signature, event ID, connection ID, amount, currency, and order reference before marking it paid.

Map the checkout to your product's record

Commerce platforms can attach an order ID and line items; hotel software can attach a booking ID and stay dates; appointment products can attach a service and scheduled time; marketplaces can keep one connection per seller. The authorization and payment-confirmation flow stays the same.

1. Register the platform application

Open Dashboard → Platform Apps and create an application. Register the exact OAuth callback URL, a server-side payment webhook URL, and the maximum scopes the application may request.

Store the client secret on your server

XRPay displays the client secret once. Do not place it in browser code, a mobile application, analytics, or logs. Rotating it invalidates the application's active access tokens.
ScopeWhat it permits
account.readRead the connected business name, account status, and payment capabilities.
checkout.writeCreate XRPay-hosted checkout pages for that business.
checkout.readRead only the checkout sessions created through this connection.
transaction.readRead payment amount, currency, status, and transaction reference.
refund.readRead refunds created through this connection.
refund.writeRequest full or partial refunds for connected payments.
payout.readRead consolidated balances, capabilities, destinations, and payout states.
payout.writeCreate payout destinations and expiring aggregate quotes.
payout.executeReserve and execute a quoted bank or Mobile Money payout.
payroll.readRead employees, payroll runs, and per-employee payment status.
payroll.writeManage employees, invitations, run items, and payroll quotes.
payroll.executeApprove and execute payroll from the merchant's composite provider balance.
webhook.readReceive signed events for payments created through this connection.
webhook.writeReplay failed deliveries or manage additional mode-scoped endpoints.
customer.readRead customer records in the connected merchant account.
customer.writeCreate customer records in the connected merchant account.
invoice.readRead invoices and Share payment-split records.
invoice.writeCreate invoices and manage Share payment-split workflows.
product.readRead the merchant's product catalog.
product.writeCreate products in the merchant's catalog.
x402.readRead x402 resources and usage.
x402.writeCreate x402-protected resources.

2. Redirect the business owner to XRPay

Create a new state value and PKCE verifier for every connection attempt. Keep both in the signed-in user's server-side session and send only the S256 challenge to XRPay.

TYPESCRIPT
import crypto from "node:crypto";

const state = crypto.randomBytes(32).toString("base64url");
const verifier = crypto.randomBytes(48).toString("base64url");
const challenge = crypto
  .createHash("sha256")
  .update(verifier)
  .digest("base64url");

// Store state and verifier in the signed-in user's server-side session.
const url = new URL("https://www.xrpay.it/connect/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: process.env.XRPAY_CONNECT_CLIENT_ID!,
  redirect_uri: "https://platform.example.com/integrations/xrpay/callback",
  scope: "account.read checkout.read checkout.write transaction.read refund.read refund.write payout.read payout.write payout.execute payroll.read payroll.write payroll.execute webhook.read webhook.write",
  state,
  code_challenge: challenge,
  code_challenge_method: "S256",
  mode: "test"
}).toString();

return redirect(url.toString());

Production callback URLs must use HTTPS and match a registered URL exactly. HTTP is accepted only for localhost development. Use mode=test until the checkout and webhook paths pass your integration tests.

3. Exchange the five-minute authorization code

First compare the returned state with the value in the server-side session. Then exchange the code from your server with the same redirect URL and the original PKCE verifier.

TYPESCRIPT
// First compare callback state with the value in the server-side session.
const response = await fetch("https://api.xrpay.it/api/connect/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    client_id: process.env.XRPAY_CONNECT_CLIENT_ID!,
    client_secret: process.env.XRPAY_CONNECT_CLIENT_SECRET!,
    code: callbackUrl.searchParams.get("code")!,
    redirect_uri: "https://platform.example.com/integrations/xrpay/callback",
    code_verifier: verifier
  })
});

const connected = await response.json();
// Encrypt access_token, refresh_token, and webhook_signing_secret at rest.
// Save connection_id and xrpay_account_id against this customer business.

The response includes a one-hour access token and a refresh token that expires after 90 days. The webhook signing secret appears only in the first code exchange response.

4. Read the business's available payment methods

Check capabilities after connection and before displaying payment settings in your product. The response reports XRP, RLUSD, cards, mobile money, bank, other supported crypto, and merchant settlement destinations separately.

TYPESCRIPT
const response = await fetch(
  "https://api.xrpay.it/api/v1/connect/capabilities",
  { headers: { Authorization: "Bearer " + connectedBusiness.xrpayAccessToken } }
);

const capabilities = await response.json();

// Show only methods whose status is active.
// If status is setup_required, show the returned requirement to the business owner.

Availability belongs to the connected business

Availability comes from that business's ready merchant rail accounts. The platform's own country and balance do not determine the connected merchant's methods or settlement destination.

5. Create one hosted checkout for the order

Create the checkout from your server. The idempotency key should describe one order payment attempt and remain unchanged when your server retries the same request.

TYPESCRIPT
const response = await fetch("https://api.xrpay.it/api/v1/payment-intents", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + connectedBusiness.xrpayAccessToken,
    "Content-Type": "application/json",
    "Idempotency-Key": "order:" + order.id + ":payment:v1"
  },
  body: JSON.stringify({
    amount_minor: 12500,
    currency: "USD",
    external_order_id: order.id,
    order_number: order.reference,
    integration_type: "api",
    payment_method_types: ["card", "bank", "mobile_money"],
    customer_email: customer.email,
    success_url: "https://platform.example.com/payment-return",
    cancel_url: "https://platform.example.com/payment-cancelled",
    metadata: {
      business_id: connectedBusiness.id,
      order_id: order.id,
      service_date: order.serviceDate
    },
    line_items: [
      { name: "Service deposit", quantity: 1, unit_amount_minor: 85000 }
    ]
  })
});

const paymentIntent = await response.json();
return redirect(paymentIntent.next_action.hosted_url);

A retry with the same connected account and Idempotency-Key returns the original checkout instead of creating a second payment. Metadata may be up to 10 KB and line items may contain up to 100 entries.

Connect provides delegated payments, not a shared balance

The connected business owns the payment. Card, bank, and mobile-money funds settle through its configured provider account; XRP and RLUSD settle to its wallet. Your platform receives scoped status and reconciliation access.

A browser return is not payment confirmation

The customer may close the tab or edit a return request. Mark the order paid only after verifying a signed XRPay webhook or retrieving the checkout status from your server.

Optional: collect Mobile Money directly

If your platform owns the payment UI, create a direct Mobile Money intent instead of redirecting to XRPay. XRPay sends the approval request to the customer's phone and returns only a masked number plus the next action; the underlying payment provider remains abstracted.

TYPESCRIPT
const response = await fetch("https://api.xrpay.it/api/v1/payment-intents", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + connectedBusiness.xrpayAccessToken,
    "Content-Type": "application/json",
    "Idempotency-Key": "order:" + order.id + ":direct-momo:v1"
  },
  body: JSON.stringify({
    amount_minor: 10000,
    currency: "GHS",
    external_order_id: order.id,
    integration_type: "api",
    customer_email: customer.email,
    collection_mode: "direct",
    payment_method_types: ["mobile_money"],
    payment_method_data: {
      type: "mobile_money",
      mobile_money: { country: "GH", network: "mtn", phone: "+233599233665" }
    }
  })
});

const paymentIntent = await response.json();
// Show next_action.message. Mark the order paid only after payment.confirmed.

Treat approve_on_phone as customer action, submit_otp as a request to call the confirmation endpoint, and wait_for_confirmation as an instruction to poll the same intent or wait for its signed event. Never create a second intent merely because submission timed out.

Sandbox sends no phone prompt

With a test access token, the same request returns simulate_payment. Complete it through the simulation endpoint; no Mobile Money network or external payment provider is contacted.

6. Verify the signed payment event

XRPay signs the exact raw request body with HMAC-SHA256 and sends the value in X-XRPay-Signature. Parse JSON only after verifying the signature.

TYPESCRIPT
const expected = "sha256=" + crypto
  .createHmac("sha256", connectedBusiness.xrpayWebhookSecret)
  .update(rawRequestBody)
  .digest("hex");

const received = request.headers["x-xrpay-signature"];
const valid = typeof received === "string" &&
  Buffer.byteLength(received) === Buffer.byteLength(expected) &&
  crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));

if (!valid) return new Response("Invalid signature", { status: 401 });

const event = JSON.parse(rawRequestBody);
if (await webhookEvents.exists(event.id)) return new Response("OK");

if (event.event === "payment.confirmed") {
  const order = await orders.find(event.data.order_id);
  if (
    event.livemode !== connectedBusiness.xrpayLivemode ||
    event.platform_connection_id !== order.xrpayConnectionId ||
    event.data.fiat_amount !== order.amount ||
    event.data.fiat_currency !== order.currency
  ) throw new Error("Payment does not match the order");

  await orders.markPaid(order.id, event.data.transaction_id);
  await webhookEvents.record(event.id);
}

return new Response("OK");

Store each webhook_id before applying a financial state change so a retry cannot mark the order paid twice. A connected application receives only events created through its connection.

7. Rotate the refresh token

Refresh before the one-hour access token expires or after an API request returns an expired-token authentication error. Every successful refresh returns a replacement refresh token and invalidates the previous value.

TYPESCRIPT
const response = await fetch("https://api.xrpay.it/api/connect/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "refresh_token",
    client_id: process.env.XRPAY_CONNECT_CLIENT_ID!,
    client_secret: process.env.XRPAY_CONNECT_CLIENT_SECRET!,
    refresh_token: connectedBusiness.xrpayRefreshToken
  })
});

const tokens = await response.json();
// Replace the stored refresh token. The previous value no longer works.

Optional: create and execute payouts

Payout access is split deliberately. payout.read inspects balances and status; payout.write manages destinations and quotes without moving money; only payout.execute can submit a quoted payout from the connected merchant's provider balance.

TYPESCRIPT
const headers = {
  Authorization: "Bearer " + connectedBusiness.xrpayAccessToken,
  "Content-Type": "application/json"
};

// 1. Discover a supported bank or Mobile Money institution.
const institutions = await fetch(
  "https://api.xrpay.it/api/v1/payout-institutions?type=bank&country=US&currency=USD",
  { headers }
).then((response) => response.json());

// 2. Save the merchant-approved destination. This does not move funds.
const destination = await fetch("https://api.xrpay.it/api/v1/payout-destinations", {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "destination:merchant-bank:v1" },
  body: JSON.stringify({
    institution_id: institutions.data[0].id,
    account_holder_name: "Merchant Business LLC",
    account_number: "000123456789",
    label: "Operating account",
    make_default: true
  })
}).then((response) => response.json());

// 3. Create an expiring quote. payout.write cannot execute it.
const quote = await fetch("https://api.xrpay.it/api/v1/payout-quotes", {
  method: "POST",
  headers,
  body: JSON.stringify({
    destination_id: destination.id,
    currency: "USD",
    amount_mode: "exact",
    amount: "125.00",
    funding_source: "provider_balance"
  })
}).then((response) => response.json());

// 4. This is the money-moving call and requires payout.execute.
const payout = await fetch("https://api.xrpay.it/api/v1/payouts", {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "payout:order-842:v1" },
  body: JSON.stringify({ quote_id: quote.id })
}).then((response) => response.json());

Test payouts use virtual balances only

With a test credential, create a local-currency payment intent and simulate a successful payment. The response's test_balance_credit confirms the idempotent credit to that business's isolated test payout balance. A payment configured to settle as XRP, RLUSD, or another crypto asset does not create this local-currency balance. Select the returned test institution, quote the payout, and execute it. Test payout responses include livemode: false, simulated: true, and external_transfer_sent: false. No bank or Mobile Money network is contacted, and test balances never mix with live balances.

Treat payout.execute as a money-moving permission

Request it only when your product actually submits merchant payouts. Show the quote amount, currency, fees, destination, and expiration to the business before execution. Use one stable idempotency key for each logical payout and never reuse that key for different payout parameters.

The payout API currently funds quotes from provider_balance. Use decimal strings such as "125.00" for payout amounts. Query the payout status after submission; do not infer completion from the initial HTTP response.

No generic wallet-transfer permission

XRPay Connect does not expose an arbitrary wallet-to-wallet transfer endpoint. Outbound movement is limited to a merchant-approved payout, payroll execution, or refund and requires the matching explicit scope shown on the authorization screen.

Optional: prepare, approve, and execute payroll

Payroll also separates preparation from money movement. payroll.write manages employees, invitations, run items, and quotes. Both approval and execution require payroll.execute, while the final execute endpoint submits the employee payouts.

TYPESCRIPT
const headers = {
  Authorization: "Bearer " + connectedBusiness.xrpayAccessToken,
  "Content-Type": "application/json"
};

// Create an employee and send a destination invitation (payroll.write).
const employee = await fetch("https://api.xrpay.it/api/v1/payroll/employees", {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "employee:ana:v1" },
  body: JSON.stringify({
    name: "Ana Rivera",
    email: "ana@example.com",
    salary: "2500.00",
    currency: "USD"
  })
}).then((response) => response.json());

await fetch(
  "https://api.xrpay.it/api/v1/payroll/employees/" + employee.id + "/invitations",
  { method: "POST", headers: { ...headers, "Idempotency-Key": "invite:ana:v1" } }
);

// After the employee has a ready payout destination, prepare and quote the run.
const run = await fetch("https://api.xrpay.it/api/v1/payroll/runs", {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "payroll:2026-08:v1" },
  body: JSON.stringify({ employee_ids: [employee.id], currency: "USD" })
}).then((response) => response.json());

await fetch("https://api.xrpay.it/api/v1/payroll/runs/" + run.id + "/quote", {
  method: "POST",
  headers
});

// Approval and execution are separate, idempotent payroll.execute operations.
await fetch("https://api.xrpay.it/api/v1/payroll/runs/" + run.id + "/approve", {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "payroll:2026-08:approve:v1" }
});

const executed = await fetch(
  "https://api.xrpay.it/api/v1/payroll/runs/" + run.id + "/execute",
  {
    method: "POST",
    headers: { ...headers, "Idempotency-Key": "payroll:2026-08:execute:v1" }
  }
).then((response) => response.json());

Approval and execution are separate calls

Do not execute until every intended employee, amount, currency, destination, and quoted fee has been reviewed. Give the approval and execution calls different idempotency keys, and poll the run or its items until every transfer reaches a terminal state.

Optional: request and track refunds

Use refund.write to request a refund only for a payment created through the current connection. A partial refund uses a positive integer amount_minor; omit it to request the full remaining refundable amount.

TYPESCRIPT
const refund = await fetch("https://api.xrpay.it/api/v1/refunds", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + connectedBusiness.xrpayAccessToken,
    "Content-Type": "application/json",
    "Idempotency-Key": "refund:order-842:v1"
  },
  body: JSON.stringify({
    payment_intent_id: connectedPaymentIntent.id,
    amount_minor: 2500,
    reason: "Customer returned one item",
    external_refund_id: "return-193"
  })
}).then((response) => response.json());

// Omit amount_minor to request the full remaining refundable amount.
// If next_action is present, send the merchant to its dashboard_url to approve
// and sign the non-custodial refund.

A refund request may return next_action. Direct non-custodial XRPL refunds require the merchant to approve and sign from the returned dashboard URL. Provider-backed payments follow the provider's reversal flow. Read the refund until it reaches completed, failed, or canceled.

Endpoints, required scopes, and effects

Connection and account

MethodPathRequired scopePurpose
GET/connect/authorizeAsk the business owner to approve scopes; requires state and S256 PKCE.
POST/api/connect/tokenExchange a code or rotate a refresh token.
GET/api/v1/connect/accountaccount.readRead the account, approved scopes, mode, and connection status.
GET/api/v1/connect/capabilitiesaccount.readRead connected payment methods and setup requirements.
GET/api/v1/balanceaccount.readRead account totals, record counts, and XRPL wallet summary.
GET/api/v1/capabilitiesaccount.readRead the general account capability summary.
POST/api/v1/connect/revokeaccount.readRevoke this connection and invalidate its tokens.

Checkout and transactions

MethodPathRequired scopePurpose
GET/api/v1/payment-intentscheckout.readList this connection's payment intents with cursor and status filters.
POST/api/v1/payment-intentscheckout.writeCreate an idempotent hosted checkout or direct Mobile Money request.
GET/api/v1/payment-intents/{id}checkout.readRead one payment intent owned by this connection.
POST/api/v1/payment-intents/{id}/cancelcheckout.writeCancel an eligible payment intent.
POST/api/v1/payment-intents/{id}/confirmcheckout.writeSubmit an OTP only when a direct Mobile Money next_action requests it.
POST/api/v1/payment-intents/{id}/simulatecheckout.writeSimulate an outcome; a local-currency success credits the isolated test payout balance.
GET/api/v1/transactionstransaction.readList connected transactions with cursor, status, and date filters.
GET/api/v1/sessionscheckout.readList legacy checkout sessions.
POST/api/v1/sessionscheckout.writeCreate a legacy checkout session; prefer payment intents for new work.

Payouts

MethodPathRequired scopePurpose
GET/api/v1/balancespayout.readRead payout balances, optionally for one destination.
GET/api/v1/payout-capabilitiespayout.readCheck payout support by country, currency, and destination type.
GET/api/v1/payout-institutionspayout.readList supported banks or Mobile Money operators.
GET/api/v1/payout-destinationspayout.readList merchant payout destinations.
POST/api/v1/payout-destinationspayout.writeCreate an idempotent bank or Mobile Money destination; does not move funds.
GET/api/v1/payout-destinations/{id}payout.readRead one payout destination.
DELETE/api/v1/payout-destinations/{id}payout.writeRemove a destination with an idempotency key; does not move funds.
POST/api/v1/payout-quotespayout.writeCreate an exact or maximum provider-balance payout quote.
GET/api/v1/payoutspayout.readList non-payroll payout orders.
POST/api/v1/payoutspayout.executeExecute an idempotent payout; test mode changes only virtual balances and sends no external transfer.
GET/api/v1/payouts/{id}payout.readRead payout status and its provider legs.

Payroll

MethodPathRequired scopePurpose
GET/api/v1/payroll/employeespayroll.readList payroll employees and destination readiness.
POST/api/v1/payroll/employeespayroll.writeCreate an idempotent employee record.
GET/api/v1/payroll/employees/{id}payroll.readRead one employee and payout destinations.
PATCH/api/v1/payroll/employees/{id}payroll.writeUpdate name, salary, or active status with an idempotency key.
DELETE/api/v1/payroll/employees/{id}payroll.writeDeactivate an employee with an idempotency key.
POST/api/v1/payroll/employees/{id}/invitationspayroll.writeCreate an idempotent destination-setup invitation.
GET/api/v1/payroll/runspayroll.readList payroll runs.
POST/api/v1/payroll/runspayroll.writeCreate an idempotent provider-balance payroll run.
GET/api/v1/payroll/runs/{id}payroll.readRead one run and its employee payments.
GET/api/v1/payroll/runs/{id}/itemspayroll.readList the run's employee payment items.
POST/api/v1/payroll/runs/{id}/itemspayroll.writeAdd an employee to a run with an idempotency key.
POST/api/v1/payroll/runs/{id}/quotepayroll.writeQuote every payable item; does not move funds.
POST/api/v1/payroll/runs/{id}/approvepayroll.executeApprove a quoted run with an idempotency key; does not submit transfers yet.
POST/api/v1/payroll/runs/{id}/executepayroll.executeExecute an approved run with an idempotency key; moves funds.

Refunds

MethodPathRequired scopePurpose
GET/api/v1/refundsrefund.readList this connection's refunds with cursor and payment-intent filters.
POST/api/v1/refundsrefund.writeRequest an idempotent full or partial refund.
GET/api/v1/refunds/{id}refund.readRead refund status and any merchant approval action.
POST/api/v1/refunds/{id}/simulaterefund.writeSimulate completed or failed in test mode only.

Customers, invoices, products, Share, and x402

MethodPathRequired scopePurpose
GET/api/v1/customerscustomer.readList and filter customer records.
POST/api/v1/customerscustomer.writeCreate a customer record.
GET/api/v1/invoicesinvoice.readList and filter invoices.
POST/api/v1/invoicesinvoice.writeCreate an invoice.
GET/api/v1/relayinvoice.readList Share payment-split records.
POST/api/v1/relayinvoice.writeCreate a Share payment-split record.
GET/api/v1/relay/{id}invoice.readRead a Share record and its flows.
POST/api/v1/relay/{id}invoice.writePropose, accept, reject, withdraw, or confirm a Share flow.
GET/api/v1/productsproduct.readList and filter products.
POST/api/v1/productsproduct.writeCreate a product.
GET/api/v1/x402x402.readList x402-protected resources and usage.
POST/api/v1/x402x402.writeCreate an x402-protected resource.

Webhooks

MethodPathRequired scopePurpose
GET/api/v1/webhookswebhook.readList mode-scoped webhook endpoints.
POST/api/v1/webhookswebhook.writeCreate a mode-scoped webhook endpoint.
DELETE/api/v1/webhooks?id={id}webhook.writeDelete a mode-scoped webhook endpoint.
POST/api/v1/webhooks/{id}/secretwebhook.writeRotate an endpoint signing secret.
GET/api/v1/webhook-deliverieswebhook.readInspect delivery health for this connection and mode.
POST/api/v1/webhook-deliverieswebhook.writeReplay one failed delivery by delivery_id.

A missing scope returns a permission error

A connected access token can call only endpoints covered by its approved scopes and only for its connected merchant, mode, and connection-owned records. Missing scopes return a permission error; revoked or expired tokens return an authentication error.

Errors your integration should handle

OAuth token errors use error and error_description. Authenticated API errors use an error object with a type and message.

ErrorWhat it means
invalid_requestA required OAuth value is missing, state is shorter than 16 characters, or S256 PKCE is absent.
invalid_clientThe client ID is unavailable or client authentication failed.
invalid_redirect_uriThe callback does not exactly match the registered HTTPS URL.
invalid_scopeThe request includes a scope the application is not configured to use.
invalid_grantThe code or refresh token is invalid, expired, revoked, already used, or paired with the wrong verifier.
access_deniedThe business owner declined the connection.
authentication_errorThe API access token is missing, expired, invalid, or revoked.
permission_errorThe access token does not include the scope required by the endpoint.
rate_limit_errorThe platform exceeded the request limit returned in the response.

Connection states

StateWhat your platform should do
activeThe token works. Allow checkout creation only when payments_enabled and a requested method are active.
setup_requiredShow the requirement returned by the capability endpoint before enabling checkout.
restrictedStop checkout creation and direct the business owner to XRPay to resolve the account restriction.
revokedDelete stored access and refresh tokens, show Connect XRPay again, and require a new authorization.

Business owners can revoke access from Dashboard → Connected Apps. Your platform can revoke its current connection with POST /api/v1/connect/revoke.

Before switching to live payments

  • Run the redirect, code exchange, checkout, and webhook verification with mode=test.
  • Confirm a retry with the same idempotency key returns the same checkout ID.
  • Reject a webhook whose signature, connection ID, amount, currency, or order reference does not match.
  • Store webhook IDs and prove that replaying the same event does not apply the payment twice.
  • If you request refund.write, test full and partial refunds plus any merchant approval action.
  • If you request payout scopes, prove that payout.write cannot execute a payout and that payout retries remain idempotent.
  • If you request payroll scopes, verify preparation, quote, approval, execution, and per-employee terminal status separately.
  • Rotate a refresh token and confirm the previous value no longer works.
  • Revoke the connection from the XRPay dashboard and confirm both access and refresh tokens stop working.

Create the application

The platform account creates the application once. Each customer business then approves its own connection and keeps control of its XRPay account, settlement settings, and revocation.

API ReferencePayments & CheckoutAuthentication and webhook verification

Need help matching XRPay events to your order model? Send the callback URL, requested scopes, and test checkout flow to XRPay support.