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
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.
A business owner chooses Connect XRPay
Your platform creates state and PKCE values, then redirects the owner to XRPay.
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.
Your server receives a single-use code
XRPay returns the code to the exact registered callback URL. The code expires after five minutes.
Your server stores connected-account tokens
Exchange the code with the client secret and PKCE verifier. The business never copies an XRPay API key.
Your platform creates a hosted checkout
Send the order amount, currency, reference, line items, and one stable idempotency key.
The customer chooses an available payment method
XRPay shows the intersection of methods requested by the platform and ready merchant-owned rails.
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
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
| Scope | What it permits |
|---|---|
account.read | Read the connected business name, account status, and payment capabilities. |
checkout.write | Create XRPay-hosted checkout pages for that business. |
checkout.read | Read only the checkout sessions created through this connection. |
transaction.read | Read payment amount, currency, status, and transaction reference. |
refund.read | Read refunds created through this connection. |
refund.write | Request full or partial refunds for connected payments. |
payout.read | Read consolidated balances, capabilities, destinations, and payout states. |
payout.write | Create payout destinations and expiring aggregate quotes. |
payout.execute | Reserve and execute a quoted bank or Mobile Money payout. |
payroll.read | Read employees, payroll runs, and per-employee payment status. |
payroll.write | Manage employees, invitations, run items, and payroll quotes. |
payroll.execute | Approve and execute payroll from the merchant's composite provider balance. |
webhook.read | Receive signed events for payments created through this connection. |
webhook.write | Replay failed deliveries or manage additional mode-scoped endpoints. |
customer.read | Read customer records in the connected merchant account. |
customer.write | Create customer records in the connected merchant account. |
invoice.read | Read invoices and Share payment-split records. |
invoice.write | Create invoices and manage Share payment-split workflows. |
product.read | Read the merchant's product catalog. |
product.write | Create products in the merchant's catalog. |
x402.read | Read x402 resources and usage. |
x402.write | Create 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.
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.
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.
Availability belongs to the connected business
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.
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
A browser return is not payment confirmation
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.
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
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.
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.
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.
Test payouts use virtual balances only
Treat payout.execute as a money-moving permission
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
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.
Approval and execution are separate calls
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.
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
| Method | Path | Required scope | Purpose |
|---|---|---|---|
GET | /connect/authorize | — | Ask the business owner to approve scopes; requires state and S256 PKCE. |
POST | /api/connect/token | — | Exchange a code or rotate a refresh token. |
GET | /api/v1/connect/account | account.read | Read the account, approved scopes, mode, and connection status. |
GET | /api/v1/connect/capabilities | account.read | Read connected payment methods and setup requirements. |
GET | /api/v1/balance | account.read | Read account totals, record counts, and XRPL wallet summary. |
GET | /api/v1/capabilities | account.read | Read the general account capability summary. |
POST | /api/v1/connect/revoke | account.read | Revoke this connection and invalidate its tokens. |
Checkout and transactions
| Method | Path | Required scope | Purpose |
|---|---|---|---|
GET | /api/v1/payment-intents | checkout.read | List this connection's payment intents with cursor and status filters. |
POST | /api/v1/payment-intents | checkout.write | Create an idempotent hosted checkout or direct Mobile Money request. |
GET | /api/v1/payment-intents/{id} | checkout.read | Read one payment intent owned by this connection. |
POST | /api/v1/payment-intents/{id}/cancel | checkout.write | Cancel an eligible payment intent. |
POST | /api/v1/payment-intents/{id}/confirm | checkout.write | Submit an OTP only when a direct Mobile Money next_action requests it. |
POST | /api/v1/payment-intents/{id}/simulate | checkout.write | Simulate an outcome; a local-currency success credits the isolated test payout balance. |
GET | /api/v1/transactions | transaction.read | List connected transactions with cursor, status, and date filters. |
GET | /api/v1/sessions | checkout.read | List legacy checkout sessions. |
POST | /api/v1/sessions | checkout.write | Create a legacy checkout session; prefer payment intents for new work. |
Payouts
| Method | Path | Required scope | Purpose |
|---|---|---|---|
GET | /api/v1/balances | payout.read | Read payout balances, optionally for one destination. |
GET | /api/v1/payout-capabilities | payout.read | Check payout support by country, currency, and destination type. |
GET | /api/v1/payout-institutions | payout.read | List supported banks or Mobile Money operators. |
GET | /api/v1/payout-destinations | payout.read | List merchant payout destinations. |
POST | /api/v1/payout-destinations | payout.write | Create an idempotent bank or Mobile Money destination; does not move funds. |
GET | /api/v1/payout-destinations/{id} | payout.read | Read one payout destination. |
DELETE | /api/v1/payout-destinations/{id} | payout.write | Remove a destination with an idempotency key; does not move funds. |
POST | /api/v1/payout-quotes | payout.write | Create an exact or maximum provider-balance payout quote. |
GET | /api/v1/payouts | payout.read | List non-payroll payout orders. |
POST | /api/v1/payouts | payout.execute | Execute an idempotent payout; test mode changes only virtual balances and sends no external transfer. |
GET | /api/v1/payouts/{id} | payout.read | Read payout status and its provider legs. |
Payroll
| Method | Path | Required scope | Purpose |
|---|---|---|---|
GET | /api/v1/payroll/employees | payroll.read | List payroll employees and destination readiness. |
POST | /api/v1/payroll/employees | payroll.write | Create an idempotent employee record. |
GET | /api/v1/payroll/employees/{id} | payroll.read | Read one employee and payout destinations. |
PATCH | /api/v1/payroll/employees/{id} | payroll.write | Update name, salary, or active status with an idempotency key. |
DELETE | /api/v1/payroll/employees/{id} | payroll.write | Deactivate an employee with an idempotency key. |
POST | /api/v1/payroll/employees/{id}/invitations | payroll.write | Create an idempotent destination-setup invitation. |
GET | /api/v1/payroll/runs | payroll.read | List payroll runs. |
POST | /api/v1/payroll/runs | payroll.write | Create an idempotent provider-balance payroll run. |
GET | /api/v1/payroll/runs/{id} | payroll.read | Read one run and its employee payments. |
GET | /api/v1/payroll/runs/{id}/items | payroll.read | List the run's employee payment items. |
POST | /api/v1/payroll/runs/{id}/items | payroll.write | Add an employee to a run with an idempotency key. |
POST | /api/v1/payroll/runs/{id}/quote | payroll.write | Quote every payable item; does not move funds. |
POST | /api/v1/payroll/runs/{id}/approve | payroll.execute | Approve a quoted run with an idempotency key; does not submit transfers yet. |
POST | /api/v1/payroll/runs/{id}/execute | payroll.execute | Execute an approved run with an idempotency key; moves funds. |
Refunds
| Method | Path | Required scope | Purpose |
|---|---|---|---|
GET | /api/v1/refunds | refund.read | List this connection's refunds with cursor and payment-intent filters. |
POST | /api/v1/refunds | refund.write | Request an idempotent full or partial refund. |
GET | /api/v1/refunds/{id} | refund.read | Read refund status and any merchant approval action. |
POST | /api/v1/refunds/{id}/simulate | refund.write | Simulate completed or failed in test mode only. |
Customers, invoices, products, Share, and x402
| Method | Path | Required scope | Purpose |
|---|---|---|---|
GET | /api/v1/customers | customer.read | List and filter customer records. |
POST | /api/v1/customers | customer.write | Create a customer record. |
GET | /api/v1/invoices | invoice.read | List and filter invoices. |
POST | /api/v1/invoices | invoice.write | Create an invoice. |
GET | /api/v1/relay | invoice.read | List Share payment-split records. |
POST | /api/v1/relay | invoice.write | Create a Share payment-split record. |
GET | /api/v1/relay/{id} | invoice.read | Read a Share record and its flows. |
POST | /api/v1/relay/{id} | invoice.write | Propose, accept, reject, withdraw, or confirm a Share flow. |
GET | /api/v1/products | product.read | List and filter products. |
POST | /api/v1/products | product.write | Create a product. |
GET | /api/v1/x402 | x402.read | List x402-protected resources and usage. |
POST | /api/v1/x402 | x402.write | Create an x402-protected resource. |
Webhooks
| Method | Path | Required scope | Purpose |
|---|---|---|---|
GET | /api/v1/webhooks | webhook.read | List mode-scoped webhook endpoints. |
POST | /api/v1/webhooks | webhook.write | Create a mode-scoped webhook endpoint. |
DELETE | /api/v1/webhooks?id={id} | webhook.write | Delete a mode-scoped webhook endpoint. |
POST | /api/v1/webhooks/{id}/secret | webhook.write | Rotate an endpoint signing secret. |
GET | /api/v1/webhook-deliveries | webhook.read | Inspect delivery health for this connection and mode. |
POST | /api/v1/webhook-deliveries | webhook.write | Replay one failed delivery by delivery_id. |
A missing scope returns a permission 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.
| Error | What it means |
|---|---|
invalid_request | A required OAuth value is missing, state is shorter than 16 characters, or S256 PKCE is absent. |
invalid_client | The client ID is unavailable or client authentication failed. |
invalid_redirect_uri | The callback does not exactly match the registered HTTPS URL. |
invalid_scope | The request includes a scope the application is not configured to use. |
invalid_grant | The code or refresh token is invalid, expired, revoked, already used, or paired with the wrong verifier. |
access_denied | The business owner declined the connection. |
authentication_error | The API access token is missing, expired, invalid, or revoked. |
permission_error | The access token does not include the scope required by the endpoint. |
rate_limit_error | The platform exceeded the request limit returned in the response. |
Connection states
| State | What your platform should do |
|---|---|
active | The token works. Allow checkout creation only when payments_enabled and a requested method are active. |
setup_required | Show the requirement returned by the capability endpoint before enabling checkout. |
restricted | Stop checkout creation and direct the business owner to XRPay to resolve the account restriction. |
revoked | Delete 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.
Need help matching XRPay events to your order model? Send the callback URL, requested scopes, and test checkout flow to XRPay support.