XRPayDocs
SupportStart free
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, reviews each scope, and adds a settlement address if one is missing.

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 methods enabled for that business, country, currency, plan, and configured providers.

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 the payment amount, currency, status, and transaction reference.
webhook.readReceive signed events for payments created through this connection.

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://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 webhook.read",
  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://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, other supported crypto, settlement, and payout separately.

TYPESCRIPT
const response = await fetch(
  "https://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

Provider-backed methods depend on that business's country, currency, verification, XRPay plan, and the providers configured in the XRPay environment. Do not promise a method until its capability status is active.

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://xrpay.it/api/v1/sessions", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + connectedBusiness.xrpayAccessToken,
    "Content-Type": "application/json",
    "Idempotency-Key": "order:" + order.id + ":payment:v1"
  },
  body: JSON.stringify({
    amount: 850,
    currency: "GHS",
    order_id: order.id,
    order_number: order.reference,
    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, amount: 850 }
    ]
  })
});

const checkout = await response.json();
return redirect(checkout.checkout_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.

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.

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.webhook_id)) return new Response("OK");

if (event.event === "payment.confirmed") {
  const order = await orders.find(event.data.order_id);
  if (
    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.webhook_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://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.

Endpoint reference

MethodPathPurpose
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/accountRead the connected account, scopes, mode, and connection status.
GET/api/v1/connect/capabilitiesRead which payment methods are active or need setup.
POST/api/v1/sessionsCreate one hosted checkout. Connected platforms must send Idempotency-Key.
GET/api/v1/sessions?id=…Read a checkout created through the current connection.
POST/api/v1/connect/revokeRevoke the current connection and invalidate its tokens.

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
activeAllow checkout creation and continue checking individual payment-method capabilities.
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

  • Complete the full authorization flow 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.
  • 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 & CheckoutSecurity & Trust

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