RushPay · REST API
RushPay REST API — accept payments, automate with webhooks, and integrate checkout with the payment widget.
RushPay’s API lets your backend accept money into your merchant wallet, confirm what was paid, and automate follow-up work. Typical uses include checkout on your site, WooCommerce stores, marketplace flows, and operational tooling—all over HTTPS with API keys or OAuth.
POST /payments/create), complete them with the Payment Widget (wallet, mobile money, card, gift cards) or server-driven flows, then verify status (GET /payments/verify).GET /transactions and your merchant balance (GET /balances).POST /customer-auth/send-otp → verify-otp → GET /customer/balances).POST /payments/send where that fits your product.POST /giftcards/validate-for-listing), then confirm a sold card (POST /giftcards/confirm-external-sale).Match your project to a starting point; each card links to the right tab or section on this page.
Install the RushPay plugin, paste your API Key, set the API base URL, and configure webhooks so orders update when checkout completes.
From your server: create a payment (POST /payments/create), issue a widget session (POST /payments/widget-session), embed the widget, then verify the payment and handle webhooks.
Do not ship your X-API-Key in the app binary. Run API calls from your backend, or open a hosted/widget checkout; follow Security essentials below.
Use the customer OTP flow so your server obtains a short-lived bearer token, then call GET /customer/balances. Keep the API key server-side only.
Also on this tab: Merchant balance vs customer balance further below.
Send funds to a RushPay user from your integration with POST /payments/send (see Payments in Endpoints).
Before listing, validate balance (POST /giftcards/validate-for-listing); after your buyer pays you, confirm the sale (POST /giftcards/confirm-external-sale). Use POST /public/giftcards/verify for public trust checks if needed.
Authenticate with X-API-Key or OAuth (POST /auth/login), then pull GET /balances and GET /transactions for dashboards, reconciliation, or alerting.
WooCommerce stores: open the WooCommerce tab on this page for gateway setup, webhooks, troubleshooting, and how order data is sent to RushPay after checkout.
GET /balances (with X-API-Key) returns the RushPay account linked to your API application—the merchant. To display a shopper’s own RushPay balance from your server (e.g. marketplace wallet), use the Customer session flow in the Endpoints tab: POST /customer-auth/send-otp → POST /customer-auth/verify-otp → GET /customer/balances with Authorization: Bearer. Keep your API key server-side only.
X-API-Key, OAuth client secret, or webhook signing secret in browser JavaScript, mobile app binaries, or public repositories. Call the API from your server, or use the widget session flow (POST /v1/payments/widget-session after POST /v1/payments/create) and initialize the widget with only widgetSessionToken + paymentReference.https://. Do not send API keys, OAuth secrets, or customer tokens over plain HTTP.CURLOPT_SSL_VERIFYPEER = false, rejectUnauthorized: false, etc.).payment_reference and amounts from browsers or third parties as untrusted until you confirm them with an authenticated GET /payments/verify (or equivalent) on your backend.X-RushPay-Signature and verify HMAC with your secret (constant-time compare, e.g. PHP hash_equals); reject requests with missing or invalid signatures..env and local secrets out of git and backups that are publicly accessible.GET /v1/payments/widget-context with X-RushPay-Widget-Session). Production API configuration must allow your storefront’s HTTPS origin and CORS preflight must permit that header—otherwise the widget sees network or method errors. Server-to-server tests can pass while browsers fail until this is set.X-RateLimit-Limit – maximum requests allowed.X-RateLimit-Remaining – remaining requests in current window.X-RateLimit-Reset – unix timestamp when window resets.Additional per-endpoint limits apply to abuse-sensitive routes (OTP and wallet completion). Limit keys include payments/send-otp, payments/verify-otp, payments/process-widget, and payments/process (typically lower per-minute than the default). If you receive 429 Too Many Requests, wait and retry with backoff—do not spin tight loops. Use the X-RateLimit-* response headers as the source of truth for your current quota.
{
"success": true,
"message": "Operation successful",
"data": { ... },
"timestamp": "2025-01-15T10:30:00+00:00"
}
{
"success": false,
"message": "Error message",
"errors": ["Detailed error 1", "Detailed error 2"],
"timestamp": "2025-01-15T10:30:00+00:00"
}
{
"success": true,
"message": "Data retrieved successfully",
"data": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"pages": 5
},
"timestamp": "2025-01-15T10:30:00+00:00"
}
200 – Success201 – Created400 – Bad Request (validation error)401 – Unauthorized (invalid credentials)403 – Forbidden404 – Not Found405 – Method Not Allowed (e.g. using POST where only GET is defined, such as GET /v1/payments/widget-context)429 – Rate Limit Exceeded500 – Internal Server Error503 – Service Unavailable (e.g. misconfiguration such as missing CORS allowlist in production or a temporarily unavailable dependency)Put your key in a .env file (add .env to .gitignore), e.g. RUSHPAY_API_KEY=..., then load it using your stack’s dotenv support or your host’s secret injection. For a one-off terminal test, export RUSHPAY_API_KEY='...' in that shell session only—do not commit exports.
To take payments in the browser with the official widget: your backend creates the payment, requests a widget session, then your page calls RushPay.init with no API key in JavaScript. Copy-paste examples are in the Payment Widget tab (server commands + HTML).
Use the curls below for quick API checks (balances, etc.). They are not the full checkout flow.
# RUSHPAY_API_KEY must be set in this shell (export manually, direnv, or CI secrets — not committed to repo)
curl -X GET https://api.rushpay.cash/v1/balances \
-H "X-API-Key: ${RUSHPAY_API_KEY}"
Load RUSHPAY_CLIENT_ID and RUSHPAY_CLIENT_SECRET from .env (same rules as the API key).
# 1. Get access token
curl -X POST https://api.rushpay.cash/v1/auth/login \
-H "Content-Type: application/json" \
-d "{
\"client_id\": \"${RUSHPAY_CLIENT_ID}\",
\"client_secret\": \"${RUSHPAY_CLIENT_SECRET}\",
\"grant_type\": \"client_credentials\"
}"
# 2. Use access token (paste token or capture from JSON)
curl -X GET https://api.rushpay.cash/v1/balances \
-H "Authorization: Bearer ${RUSHPAY_ACCESS_TOKEN}"
RushPay API supports two authentication methods: API Key and OAuth 2.0.
Include your API key in the X-API-Key header:
X-API-Key: <value from RUSHPAY_API_KEY or your secret store>
# Recommended: define RUSHPAY_API_KEY in a .env file (never commit .env)
# Load in shell, then:
curl -X GET https://api.rushpay.cash/v1/balances \
-H "X-API-Key: ${RUSHPAY_API_KEY}"
.env and environment variables).env file locally: RUSHPAY_API_KEY=your_key_from_dashboard. Add .env to .gitignore so it is never pushed.getenv('RUSHPAY_API_KEY') or libraries like vlucas/phpdotenv; in Node, process.env.RUSHPAY_API_KEY with dotenv; in Python, os.environ with python-dotenv.OAuth 2.0 provides token-based authentication with access and refresh tokens. Store client_id and client_secret in the same way as the API key—e.g. RUSHPAY_CLIENT_ID and RUSHPAY_CLIENT_SECRET in .env (gitignored)—never commit them.
curl -X POST https://api.rushpay.cash/v1/auth/login \
-H "Content-Type: application/json" \
-d "{
\"client_id\": \"${RUSHPAY_CLIENT_ID}\",
\"client_secret\": \"${RUSHPAY_CLIENT_SECRET}\",
\"grant_type\": \"client_credentials\"
}"
Export RUSHPAY_CLIENT_ID and RUSHPAY_CLIENT_SECRET from your .env or secret store before running the command.
Authorization: Bearer your_access_token_here
curl -X GET https://api.rushpay.cash/v1/balances \
-H "Authorization: Bearer your_access_token_here"
curl -X POST https://api.rushpay.cash/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "def50200a1b2c3d4e5f6..."
}'
curl -X POST https://api.rushpay.cash/v1/auth/revoke \
-H "Content-Type: application/json" \
-d '{
"token": "token_to_revoke"
}'
Use API Key if:
Use OAuth 2.0 if:
POST /auth/login – Get OAuth 2.0 access token.POST /auth/refresh – Refresh access token.POST /auth/revoke – Revoke access or refresh token.Create hosted invoices with line items. Supported modes include invoice_type individual or multi, and amount_mode fixed or open, with optional open_amount_min / open_amount_max (GHS) when your integration has open-amount invoicing enabled.
Checkout: For individual + fixed, sending the draft creates one payment request and the hosted page embeds the widget with that reference. For open amount or multi, the hosted invoice page creates a new pending payment per payer on your RushPay site, then loads the widget. Each completion still triggers payment.completed.
Webhooks: invoice.paid when an individual invoice is settled (status becomes paid). With multi-pay, you receive invoice.payment_received for each successful payer (payload includes amount_collected and payment_count); the invoice row stays sent.
GET /invoices – List invoices (?status= optional: draft, sent, paid, void, cancelled; page, limit). Multi-pay listings include amount_collected and payment_count when applicable.POST /invoices – Create a draft: line_items (description, quantity, unit_amount), optional tax_amount, discount_amount, customer_*, due_date, notes, callback_url, and when enabled for your app: invoice_type, amount_mode, optional open_amount_min, open_amount_max. Response includes hosted_pay_url (site /invoice?t=…; set RUSHPAY_PUBLIC_BASE_URL when the app is not on the API host).GET /invoices/{id} – Get one invoice with line items.PATCH /invoices/{id} – Update a draft (same fields as create; line items replace when supplied).POST /invoices/{id}/send – Marks sent. For individual + fixed, creates POST /payments/create for the invoice total and returns a payment object. For open or multi, no payment is pre-created; payers start checkout from the hosted link as above. When customer_email and/or customer_phone are set, RushPay sends a branded email and/or SMS with the hosted pay link.POST /invoices/{id}/void – Void draft or cancel pending payment(s) linked to the invoice (including per-payer checkouts created from the hosted page).RushPay Escrow is a branded protected transaction / conditional settlement product — not by default a licensed third-party escrow or trust; use wording your legal counsel approves for your jurisdictions. Merchants manage deals under Escrow (/my_escrow): draft → publish (optional release_at for scheduled settlement) creates a pay link; customers pay on /escrow?t=…. Funds are held with RushPay until release to the seller or refund to the buyer when the payer’s RushPay user was recorded (some guest flows may not support auto-refund).
funded, the seller (beneficiary) may receive email/SMS if on file — in addition to merchant escrow.funded webhooks.release_at, RushPay processes due releases on a schedule (Africa/Accra). Contact RushPay support if you need help verifying scheduled settlement for your environment.escrow.funded – Checkout completed; deal is funded (payload: escrow_id, payment_reference, transaction_id, amount, optional buyer_user_id, payer_email, idempotency_key). You still receive payment.completed.escrow.released – Merchant released to seller (payload includes idempotency_key for deduplication, plus escrow_id, release_transaction_id, seller_user_id, amount, payment_reference, reference).escrow.refunded – Merchant refunded buyer (same pattern: idempotency_key plus escrow_id, refund_transaction_id, buyer_user_id, amount, payment_reference, reference).POST /payments/create – Create payment request (receive payment). Send a unique Idempotency-Key header per logical payment from your server.POST /payments/widget-session – Server-only: issue a short-lived token for the browser widget after create. Do not expose X-API-Key in JavaScript.GET /payments/widget-context – GET only; read-only payment fields for the widget. Authenticate with header X-RushPay-Widget-Session (or merchant key per product rules).POST /payments/update-merchant – Merge metadata and/or update description on an existing payment (e.g. attach store order id after checkout).GET /payments/{reference} – Get payment status.POST /payments/send – Send payment to a RushPay user.POST /payments/{reference}/cancel – Cancel a pending payment.POST /payments/check-user – Check if user exists by email (receive-payment flow).POST /payments/send-otp – Send OTP to user (email and SMS) for payment auth.POST /payments/verify-otp – Verify OTP and return user balance.POST /payments/process – Process payment (requires 4-digit account PIN). Debit amount is always the value stored on the payment from POST /payments/create (not a client-supplied amount). Optional amount in the body is checked for a match only (tamper detection).POST /payments/process-widget – Process payment from widget (requires verified email + 4-digit account PIN). Wallet completion only. Same server-authoritative amount rule as process.POST /payments/validate-giftcard-widget – Read-only: validate a gift card for a payment and return giftcard_balance, covers_amount, and currency (widget/WooCommerce verify step before process-widget-giftcard).POST /payments/process-widget-giftcard – Guest gift card checkout: payment_reference, giftcard_code, optional amount (must match the payment). No OTP or PIN. The gift card balance must fully cover the payment amount.POST /payments/initiate-mobile-money – Start mobile money charge (guest-friendly).POST /payments/initiate-card – Start card checkout via Paystack (guest-friendly). Returns access_code for Paystack InlineJS V2 resumeTransaction; poll charge-status with the returned reference after payment.POST /payments/submit-momo-otp – Submit OTP for mobile money when the gateway requests it.GET /payments/charge-status – Poll mobile money or card charge status.Mobile money & card: Poll GET/POST /payments/charge-status using the charge reference from initiate-mobile-money or initiate-card (typically TRF…), not only your merchant payment_reference. GET /payments/verify reflects RushPay’s ledger; keep polling charge-status until it returns completed or a definitive failure.
Browser widget (production): your server calls POST /payments/create then POST /payments/widget-session, and the page initializes the widget with widgetSessionToken + paymentReference only (header X-RushPay-Widget-Session on subsequent API calls). Widget flow after checkout starts: customer chooses RushPay wallet (email → OTP → balance → PIN → process-widget), Mobile money (initiate → optional OTP → poll charge-status), Card (initiate-card → Paystack popup → poll charge-status), or Pay with Giftcard (verify with validate-giftcard-widget, then process-widget-giftcard when the card covers the full amount).
X-API-Key / OAuth token. Use GET /payments/verify (or the documented payment status route) with authentication so you only read your payments. Do not rely on guessing references from untrusted clients without verifying status server-side.amount fields on completion endpoints are for mismatch checks only—send them from your server after reading the payment record, not an unverified client total.Idempotency-Key on POST /payments/create so retries do not create duplicate payments. Completing a wallet or gift-card payment is safe to retry. If the network drops, call GET /payments/verify with the payment_reference to reconcile before retrying a charge.These are different flows. Do not confuse them.
POST /giftcards/validate-for-listing — Server-to-server only. Header X-API-Key. Body JSON {"code":"…","min_balance_ghs":12.50}. Call this before saving a gift-card product so the listed price cannot exceed verified remaining balance. The gift card must be offered for external sale in RushPay (creator toggle). On failure, HTTP 400 with error_code when applicable: ALREADY_SOLD_EXTERNALLY (purchased via a partner; not for resale), NOT_OFFERED_FOR_EXTERNAL_SALE (valid on RushPay but not listed for partner resale). Success data may include offered_for_external_sale: true. Rate limit key giftcards/validate-for-listing.POST /giftcards/confirm-external-sale — Server-to-server. Header X-API-Key. Body JSON {"code":"…","marketplace_order_ref":"…","marketplace":"optional"}. After your marketplace records a paid order, call this once per gift-card line so RushPay sets externally_sold_at and unlocks spending for the buyer. marketplace_order_ref must be unique per integration (retries with the same ref are idempotent). Rate limit key giftcards/confirm-external-sale.POST /payments/validate-giftcard-widget — Checkout widget only. Authenticate with X-RushPay-Widget-Session (recommended) or merchant X-API-Key; must match the payment’s payment_reference.POST /public/giftcards/verify — Public trust check. No API key. Body JSON {"code":"…"}. Stricter IP rate limits. Response includes sale_status when valid: standard, externally_sold, or not_offered_for_resale, plus a message for buyers (resale warnings). Use this endpoint for programmatic checks; RushPay may also provide a web page for the same trust check—see the main RushPay site.While offered_for_external_sale is on and the card is not yet confirmed sold via confirm-external-sale, RushPay blocks all debits of that gift card (wallet payments, widget guest checkout, and in-app redeem) so the balance stays reserved for the partner listing.
Updates merchant-facing fields on a payment you already created. Send payment_reference plus at least one of description or metadata. Metadata is merged with existing JSON; nested metadata.woocommerce is merged with any previous value. Used by the WooCommerce plugin so the dashboard shows order numbers and line items—see the WooCommerce tab.
curl -X POST https://api.rushpay.cash/v1/payments/update-merchant \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key_here" \
-d '{"payment_reference":"APIxxxxxxxx","description":"Order #100 – Items","metadata":{"woocommerce":{"woocommerce_order_id":100,"order_number":"100"}}}'
GET /transactions – List transactions with pagination and filters.GET /transactions/{id} – Get single transaction details.GET /transactions/search – Search by reference or other fields.GET /balances – Get account balance and available balance for the RushPay user linked to your API application (merchant / API owner). Optional ?include_wallets=1.For integrations that must show a customer’s own RushPay balance (e.g. a marketplace “My Wallet” page). Use your X-API-Key only on your server for the OTP steps; the customer receives a short-lived Bearer token after verifying the code. Do not expose the merchant API key in client-side code.
POST /customer-auth/send-otp – Header X-API-Key. Body JSON {"email":"[email protected]"}. Sends the same 6-digit code by email and, when the RushPay user has a phone on file, by SMS. Response data.sms_sent is true when SMS was accepted by the gateway.POST /customer-auth/verify-otp – Header X-API-Key. Body {"email":"...","otp_code":"123456"}. Returns access_token, expires_in, token_type (Bearer), plus a balance snapshot.GET /customer/balances – Header Authorization: Bearer <access_token> only (no API key). Optional ?include_wallets=1. Returns that end user’s balance, not the merchant’s.Prerequisites: The shopper’s email must match their RushPay account. The customer-session feature must be enabled on your RushPay environment. Tokens: Treat access_token as secret; it expires after expires_in seconds (default multi-day). Sessions end when the token expires or is invalidated server-side. Security: OTP codes are single-use and time-limited; keep the merchant X-API-Key on your server; rate limits apply to the OTP endpoints.
# 1) Send OTP (server-side)
curl -X POST https://api.rushpay.cash/v1/customer-auth/send-otp \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key_here" \
-d '{"email":"[email protected]"}'
# 2) Verify OTP → customer access token
curl -X POST https://api.rushpay.cash/v1/customer-auth/verify-otp \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key_here" \
-d '{"email":"[email protected]","otp_code":"123456"}'
# 3) Customer balance (Bearer only)
curl -X GET "https://api.rushpay.cash/v1/customer/balances?include_wallets=1" \
-H "Authorization: Bearer ACCESS_TOKEN_FROM_STEP_2"
POST /webhooks/register – Register webhook URL.GET /webhooks – List registered webhooks.DELETE /webhooks – Delete a webhook.GET /accounts/profile – Get API application profile and owner.PUT /accounts/profile – Update application name, description, webhook URL.Detailed routes—including customer session (end-user balance) and POST /payments/update-merchant—are documented in this Endpoints tab. WooCommerce-specific setup is in the WooCommerce tab.
Webhooks let RushPay notify your application in real-time when important events happen, such as payments completing or failing.
curl -X POST https://api.rushpay.cash/v1/webhooks/register \
-H "Authorization: Bearer your_access_token" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yoursite.com/webhooks/rushpay"
}'
Every webhook includes an HMAC SHA256 signature in the X-RushPay-Signature header. Reject the request if the header is missing or verification fails—do not process the body first. Load the secret from getenv('RUSHPAY_WEBHOOK_SECRET') or your secret store, not from source control.
<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', $payload, $secret);
return hash_equals($expectedSignature, $signature);
}
payment.completed – Payment request completed successfully.escrow.funded – Escrow payer completed checkout; funds are in the pooled escrow ledger until release or refund.escrow.released – Escrow amount was sent from the pool to the seller.escrow.refunded – Escrow amount was returned from the pool to the buyer wallet.payment.failed – Payment failed.payment.cancelled – Payment request cancelled.transaction.completed – Send-payment transaction completed.transaction.failed – Transaction failed.2xx status quickly, then process asynchronously.X-RushPay-Event-Id (or equivalent) for idempotency so duplicate deliveries do not double-fulfill.payment.completed event can arrive while your shop order is cancelled or already settled elsewhere. Before marking an order paid or shipping goods, confirm the payment reference and amount against GET /payments/verify and your own order state. E‑commerce plugins may ignore completion webhooks when an order is no longer payable—custom integrations should follow the same rule.Each delivery includes: Content-Type: application/json, X-RushPay-Signature (HMAC SHA256 of the raw body), and X-RushPay-Event-Id (use for deduplication). Escrow events (escrow.funded, escrow.released, escrow.refunded) also include a stable idempotency_key inside data (escrow id + funding, release, or refund transaction_id as applicable) so merchants can dedupe retries even when each delivery attempt gets a new event row.
payment.completed body{
"event": "payment.completed",
"data": {
"payment_reference": "API1234567890",
"amount": 100.00,
"currency": "GHS",
"transaction_id": 123,
"paid_at": "2025-01-15T10:35:00+00:00",
"metadata": { }
},
"timestamp": "2025-01-15T10:35:00+00:00"
}
If your endpoint does not return 2xx, RushPay retries failed deliveries up to 3 times with exponential backoff (starting at 5 seconds, doubling each time), with up to 30 seconds per attempt. After all retries fail, the event is marked failed in your dashboard.
The official plugin adds a RushPay gateway to WooCommerce. The payment widget is loaded on checkout; API keys stay on the server (AJAX proxies to the RushPay API).
https://api.rushpay.cash).Customers pay with the embedded widget: RushPay wallet (check-user → OTP → balance → PIN → process-widget), Mobile money (initiate → optional OTP → charge-status), Card (initiate-card → Paystack popup → charge-status), or Pay with Giftcard (guest: verify via validate-giftcard-widget, then place order → process-widget-giftcard when balance covers the order). On place order, WooCommerce saves _rushpay_payment_reference and _rushpay_checkout_flow (wallet, giftcard, momo, or card).
POST /payments/create often runs before a WooCommerce order id exists. After the order is saved, the plugin calls:
POST /v1/payments/update-merchant
with the payment reference, a human-readable description (order number + short product list), and metadata.woocommerce including woocommerce_order_id, order_number, site_url, and line_items. If this call fails, the error is logged only; checkout still completes.
{
"payment_reference": "APIxxxxxxxx",
"description": "WooCommerce Order #12345 – Product A ×2",
"metadata": {
"woocommerce": {
"woocommerce_order_id": 12345,
"order_number": "12345",
"site_url": "https://yourstore.com",
"line_items": [
{ "name": "Product A", "quantity": 2, "sku": "SKU-1" }
]
}
}
}
update-merchant; check WooCommerce logs for RushPay update-merchant failed.X-RushPay-Signature with your secret.payment.completed delivery can arrive when the WooCommerce order is no longer in a payable state. The plugin records a note and does not force payment_complete in that case—use order status and RushPay payment status together when investigating.Full request/response fields for POST /payments/update-merchant are documented in the Endpoints tab on this page.
Complete, production-ready code samples for all RushPay API features. Simply insert your API key and run.
RUSHPAY_API_KEY (and OAuth vars) from the environment—use a .env file or host secret injection; never commit keys. Production widget: call POST /v1/payments/create then POST /v1/payments/widget-session on the server and pass only widgetSessionToken + paymentReference into the page—do not put the merchant API key in browser JavaScript. Successful receive-payment flows use status: "completed" on GET /v1/payments/verify?payment_reference=... or GET /v1/payments/{reference} (not paid). On HTTP 429, wait and retry with backoff. Optional amount on POST /payments/process must match the stored payment amount if you send it (tamper check)—the debited amount is always the server-side value from create.
<?php
/**
* Get account balance using API Key
* Set RUSHPAY_API_KEY in the environment (e.g. .env loaded by your app — never commit secrets).
*/
$apiBaseUrl = 'https://api.rushpay.cash/v1';
$apiKey = getenv('RUSHPAY_API_KEY') ?: '';
if ($apiKey === '') {
fwrite(STDERR, "Set RUSHPAY_API_KEY in the environment.\n");
exit(1);
}
$ch = curl_init($apiBaseUrl . '/balances');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
echo "Balance: " . number_format($data['data']['balance'], 2) . "\n";
} else {
echo "Request failed (HTTP $httpCode).\n"; // Do not echo raw bodies in production (may leak details).
}
<?php
/**
* Get account balance using OAuth 2.0
* Set RUSHPAY_CLIENT_ID and RUSHPAY_CLIENT_SECRET in the environment (never hard-code).
*/
$apiBaseUrl = 'https://api.rushpay.cash/v1';
$clientId = getenv('RUSHPAY_CLIENT_ID') ?: '';
$clientSecret = getenv('RUSHPAY_CLIENT_SECRET') ?: '';
if ($clientId === '' || $clientSecret === '') {
fwrite(STDERR, "Set RUSHPAY_CLIENT_ID and RUSHPAY_CLIENT_SECRET.\n");
exit(1);
}
// Step 1: Get access token
$ch = curl_init($apiBaseUrl . '/auth/login');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'client_id' => $clientId,
'client_secret' => $clientSecret,
'grant_type' => 'client_credentials'
])
]);
$response = curl_exec($ch);
$tokenData = json_decode($response, true);
curl_close($ch);
if (isset($tokenData['data']['access_token'])) {
$accessToken = $tokenData['data']['access_token'];
// Step 2: Get balance using access token
$ch = curl_init($apiBaseUrl . '/balances');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $accessToken
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
echo "Balance: " . number_format($data['data']['balance'], 2) . "\n";
}
} else {
echo "Failed to get access token\n";
}
<?php
/**
* List transactions with pagination
* Production-ready example
*/
$apiBaseUrl = 'https://api.rushpay.cash/v1';
$apiKey = getenv('RUSHPAY_API_KEY') ?: '';
if ($apiKey === '') {
fwrite(STDERR, "Set RUSHPAY_API_KEY.\n");
exit(1);
}
// Get first page of transactions (10 per page)
$url = $apiBaseUrl . '/transactions?page=1&limit=10';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
echo "Total Transactions: " . $data['data']['total'] . "\n";
echo "Page: " . $data['data']['page'] . " of " . $data['data']['total_pages'] . "\n\n";
foreach ($data['data']['transactions'] as $transaction) {
echo "Reference: " . $transaction['reference'] . "\n";
echo "Amount: " . number_format($transaction['amount'], 2) . "\n";
echo "Type: " . $transaction['type'] . "\n";
echo "Status: " . $transaction['status'] . "\n";
echo "Date: " . $transaction['created_at'] . "\n";
echo "---\n";
}
} else {
echo "Request failed (HTTP $httpCode).\n";
}
<?php
/**
* Create a payment request (receive payment from RushPay user)
* Production-ready example
*/
$apiBaseUrl = 'https://api.rushpay.cash/v1';
$apiKey = getenv('RUSHPAY_API_KEY') ?: '';
if ($apiKey === '') {
fwrite(STDERR, "Set RUSHPAY_API_KEY.\n");
exit(1);
}
// Amount stored here is what the payer will be charged (wallet / completion uses this value).
$paymentData = [
'amount' => 100.00,
'description' => 'Payment for Order #12345',
'callback_url' => 'https://yoursite.com/payment-callback',
'metadata' => [
'order_id' => '12345',
'customer_email' => '[email protected]'
]
];
$ch = curl_init($apiBaseUrl . '/payments/create');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey
],
CURLOPT_POSTFIELDS => json_encode($paymentData)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 || $httpCode === 201) {
$data = json_decode($response, true);
echo "Payment Reference: " . $data['data']['payment_reference'] . "\n";
echo "Next Step: Create widget session with this payment_reference (POST /v1/payments/widget-session)\n";
echo "Status: " . $data['data']['status'] . "\n";
echo "Expires At: " . $data['data']['expires_at'] . "\n";
} else {
echo "Request failed (HTTP $httpCode).\n";
}
Complete, production-ready payment page with RushPay Payment Widget integration. This example includes the full HTML page with widget integration, webhook handler, and callback URL examples.
<?php
/**
* Production-Ready RushPay Payment Widget Example
*
* This is a complete, production-ready example showing how to integrate
* the RushPay Payment Widget into your website.
*
* SETUP INSTRUCTIONS:
* 1. Production: On your server, call POST /v1/payments/create (with Idempotency-Key) then
* POST /v1/payments/widget-session. Pass widget_session_token and payment_reference into this
* page (e.g. RUSHPAY_WIDGET_SESSION_TOKEN + RUSHPAY_PAYMENT_REFERENCE from your app session).
* Never put the merchant API key in HTML or static JS.
* 2. Replace API host if different; set callback URL; optional webhook URL.
* 3. Customize styling to match your website.
*/
// Configuration
$rushpay_widget_session_token = getenv('RUSHPAY_WIDGET_SESSION_TOKEN') ?: '';
$rushpay_payment_reference = getenv('RUSHPAY_PAYMENT_REFERENCE') ?: '';
if ($rushpay_widget_session_token === '' || $rushpay_payment_reference === '') {
http_response_code(500);
echo 'RushPay is not configured. Set RUSHPAY_WIDGET_SESSION_TOKEN and RUSHPAY_PAYMENT_REFERENCE.';
exit;
}
$rushpay_api_base = 'https://api.rushpay.cash'; // RushPay API base URL
$rushpay_widget_base = 'https://api.rushpay.cash'; // RushPay widget base URL
// Payment Configuration
$payment_config = [
// Amount configuration - choose one:
'amount' => [
'type' => 'fixed', // Options: 'fixed', 'default', or 'user-input'
'value' => 100.00 // Required if type is 'fixed' or 'default'
],
// Callback URL - where to redirect after successful payment
'callback_url' => 'https://yoursite.com/payment-success?order_id=12345',
// Webhook URL - optional, for server-side notifications
'webhook_url' => 'https://yoursite.com/webhook/rushpay',
// Payment description
'description' => 'Payment for Order #12345'
];
// Get order ID from query string (example)
$order_id = $_GET['order_id'] ?? '12345';
// Update callback URL with order ID
$payment_config['callback_url'] = 'https://yoursite.com/payment-success?order_id=' . urlencode($order_id);
$payment_config['description'] = 'Payment for Order #' . htmlspecialchars($order_id);
// Optional: Get amount from query string or form
if (isset($_GET['amount']) && is_numeric($_GET['amount'])) {
$amount = (float)$_GET['amount'];
if ($amount > 0 && $amount <= 50000) {
$payment_config['amount']['value'] = $amount;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Complete Payment - RushPay</title>
<!-- RushPay Payment Widget CSS -->
<link rel="stylesheet" href="<?php echo htmlspecialchars($rushpay_widget_base); ?>/widget/payment-widget.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
/* Custom styles for your page */
body {
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
min-height: 100vh;
padding: 40px 20px;
margin: 0;
}
.checkout-container {
max-width: 1200px;
margin: 0 auto;
}
.checkout-header {
text-align: center;
margin-bottom: 40px;
}
.checkout-header h1 {
color: #333;
font-size: 32px;
margin-bottom: 10px;
}
.checkout-header p {
color: #666;
font-size: 16px;
}
.checkout-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
}
@media (max-width: 768px) {
.checkout-content {
grid-template-columns: 1fr;
}
}
.order-summary {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
height: fit-content;
}
.order-summary h2 {
color: #333;
font-size: 20px;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #f0f0f0;
}
.summary-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.summary-item:last-child {
border-bottom: none;
font-weight: 600;
font-size: 18px;
color: #ff6b00;
margin-top: 10px;
padding-top: 20px;
border-top: 2px solid #f0f0f0;
}
.summary-label {
color: #666;
}
.summary-value {
color: #333;
font-weight: 500;
}
.payment-section {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.payment-section h2 {
color: #333;
font-size: 20px;
margin-bottom: 20px;
}
.security-badge {
display: flex;
align-items: center;
gap: 8px;
margin-top: 20px;
padding: 12px;
background: #f9f9f9;
border-radius: 8px;
font-size: 14px;
color: #666;
}
.security-badge i {
color: #28a745;
}
.rushpay-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #ff6b00;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="checkout-container">
<div class="checkout-header">
<h1><i class="fas fa-shopping-cart"></i> Complete Your Payment</h1>
<p>Secure payment powered by RushPay</p>
</div>
<div class="checkout-content">
<!-- Order Summary -->
<div class="order-summary">
<h2>Order Summary</h2>
<div class="summary-item">
<span class="summary-label">Order ID:</span>
<span class="summary-value">#<?php echo htmlspecialchars($order_id); ?></span>
</div>
<div class="summary-item">
<span class="summary-label">Description:</span>
<span class="summary-value"><?php echo htmlspecialchars($payment_config['description']); ?></span>
</div>
<?php if ($payment_config['amount']['type'] === 'fixed' || $payment_config['amount']['type'] === 'default'): ?>
<div class="summary-item">
<span class="summary-label">Amount:</span>
<span class="summary-value">GH₵<?php echo number_format($payment_config['amount']['value'], 2); ?></span>
</div>
<?php endif; ?>
<div class="summary-item">
<span class="summary-label">Total:</span>
<span class="summary-value">
<?php if ($payment_config['amount']['type'] === 'fixed' || $payment_config['amount']['type'] === 'default'): ?>
GH₵<?php echo number_format($payment_config['amount']['value'], 2); ?>
<?php else: ?>
Enter amount
<?php endif; ?>
</span>
</div>
</div>
<!-- Payment Widget -->
<div class="payment-section">
<h2>Payment Details</h2>
<!-- RushPay Payment Widget Container -->
<div id="rushpay-payment-widget">
<div style="padding: 20px; text-align: center; color: #666;">
<div class="rushpay-spinner" style="margin: 0 auto 10px;"></div>
<p>Loading payment widget...</p>
</div>
</div>
<div class="security-badge">
<i class="fas fa-shield-alt"></i>
<span>Your payment is secured and encrypted</span>
</div>
</div>
</div>
</div>
<!-- RushPay Payment Widget JavaScript -->
<?php
// Build configuration for RushPay.init (widget-session checkout only)
$jsConfig = [
'widgetSessionToken' => $rushpay_widget_session_token,
'paymentReference' => $rushpay_payment_reference,
'callbackUrl' => $payment_config['callback_url'],
'description' => $payment_config['description'],
];
if (!empty($payment_config['webhook_url'])) {
$jsConfig['webhookUrl'] = $payment_config['webhook_url'];
}
$jsConfigJson = json_encode($jsConfig, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$apiBaseJson = json_encode($rushpay_api_base . '/v1', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
if ($jsConfigJson === false || json_last_error() !== JSON_ERROR_NONE) {
$jsConfigJson = '{"callbackUrl":"","description":""}';
}
if ($apiBaseJson === false || json_last_error() !== JSON_ERROR_NONE) {
$apiBaseJson = '"https://api.rushpay.cash/v1"';
}
?><script>
// Set API base URL before loading widget
window.RUSHPAY_API_BASE = <?php echo $apiBaseJson; ?>;
// Configuration object for widget initialization
const rushpayConfig = <?php echo $jsConfigJson; ?>;
var rushpayReady = (rushpayConfig.widgetSessionToken && rushpayConfig.paymentReference);
if (!rushpayReady) {
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('rushpay-payment-widget').innerHTML =
'' +
'
' +
'Configuration Error
' +
'Set widget session + payment reference in the PHP block at the top.' +
'';
});
} else {
// Load widget script
const widgetScript = document.createElement('script');
widgetScript.src = <?php echo json_encode($rushpay_widget_base . '/widget/payment-widget.js'); ?>;
widgetScript.onload = function() {
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeWidget);
} else {
initializeWidget();
}
};
widgetScript.onerror = function() {
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('rushpay-payment-widget').innerHTML =
'' +
'
' +
'Failed to Load Widget
' +
'Unable to load RushPay widget script. Please check the widget URL.' +
'';
});
};
document.head.appendChild(widgetScript);
function initializeWidget() {
// Check if RushPay is loaded
if (typeof RushPay === 'undefined') {
console.error('RushPay widget failed to load');
document.getElementById('rushpay-payment-widget').innerHTML =
'' +
' ' +
'RushPay widget object not found. Please check the script URL.' +
'';
return;
}
try {
// Initialize RushPay Payment Widget
RushPay.init(rushpayConfig);
} catch (error) {
console.error('RushPay initialization error:', error);
document.getElementById('rushpay-payment-widget').innerHTML =
'' +
'
' +
'Initialization Error
' +
error.message + '
' +
'Check the browser console (F12) for more details.' +
'';
}
}
}
</script>
</body>
</html>
<?php
/**
* Process payment with giftcard/voucher redemption
* Production-ready example
*/
$apiBaseUrl = 'https://api.rushpay.cash/v1';
$apiKey = getenv('RUSHPAY_API_KEY') ?: '';
if ($apiKey === '') {
fwrite(STDERR, "Set RUSHPAY_API_KEY.\n");
exit(1);
}
// Process payment with giftcard (pin required - user's 4-digit account PIN)
// Amount debited is the amount on the payment record from create, not a field here.
$paymentData = [
'email' => '[email protected]',
'payment_reference' => 'API-1234567890', // From create payment request
'pin' => '1234', // User's 4-digit account PIN (required when confirming payment)
'allow_giftcard' => true,
'giftcard_code' => 'GIFTCARD123' // User's giftcard code
];
$ch = curl_init($apiBaseUrl . '/payments/process');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey
],
CURLOPT_POSTFIELDS => json_encode($paymentData)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
if ($data['success']) {
echo "Payment successful!\n";
echo "Transaction ID: " . $data['data']['transaction_id'] . "\n";
if (!empty($data['data']['giftcard_applied'])) {
echo "Giftcard Applied: " . $data['data']['giftcard_applied'] . "\n";
echo "Amount Paid (wallet portion): " . $data['data']['amount_paid'] . "\n";
}
} else {
echo "Error: " . $data['message'] . "\n";
}
} else {
echo "Request failed (HTTP $httpCode).\n";
}
<?php
/**
* Send payment to a RushPay user (refund, payout, etc.)
* Production-ready example
*/
$apiBaseUrl = 'https://api.rushpay.cash/v1';
$apiKey = getenv('RUSHPAY_API_KEY') ?: '';
if ($apiKey === '') {
fwrite(STDERR, "Set RUSHPAY_API_KEY.\n");
exit(1);
}
$paymentData = [
'recipient_account' => '1234567890', // OR use 'recipient_email' => '[email protected]'
'amount' => 50.00,
'description' => 'Refund for Order #12345'
];
$ch = curl_init($apiBaseUrl . '/payments/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey
],
CURLOPT_POSTFIELDS => json_encode($paymentData)
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
if ($data['success']) {
echo "Payment sent successfully!\n";
echo "Transaction ID: " . $data['data']['transaction_id'] . "\n";
echo "Reference: " . $data['data']['reference'] . "\n";
echo "Recipient: " . $data['data']['recipient_name'] . "\n";
} else {
echo "Error: " . $data['message'] . "\n";
}
} else {
echo "Request failed (HTTP $httpCode).\n";
}
<?php
/**
* Verify payment status (GET /v1/payments/verify?payment_reference=...)
* Same as GET /v1/payments/{reference} — scoped to your API application via X-API-Key.
*/
$apiBaseUrl = 'https://api.rushpay.cash/v1';
$apiKey = getenv('RUSHPAY_API_KEY') ?: '';
if ($apiKey === '') {
fwrite(STDERR, "Set RUSHPAY_API_KEY.\n");
exit(1);
}
$paymentReference = 'API-1234567890'; // From create payment request
$ch = curl_init($apiBaseUrl . '/payments/verify?' . http_build_query(['payment_reference' => $paymentReference]));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
echo "Payment Reference: " . $data['data']['payment_reference'] . "\n";
echo "Status: " . $data['data']['status'] . "\n";
echo "Amount: " . number_format($data['data']['amount'], 2) . "\n";
if (($data['data']['status'] ?? '') === 'completed') {
echo "Paid At: " . ($data['data']['paid_at'] ?? '') . "\n";
echo "Transaction ID: " . ($data['data']['transaction_id'] ?? '') . "\n";
}
} elseif ($httpCode === 429) {
echo "Rate limited — retry later with backoff.\n";
} else {
echo "Request failed (HTTP $httpCode).\n";
}
Production rule: create the payment and widget session on your server with X-API-Key. Pass only widgetSessionToken + paymentReference into the page—never put the merchant API key in browser JavaScript. The hosted script payment-widget.js loads the amount via GET /v1/payments/widget-context.
POST /v1/payments/create → save data.payment_reference.POST /v1/payments/widget-session with that reference → save data.widget_session_token.Use your real API key only here—never in frontend source. Replace amount, URLs, and idempotency key.
export RUSHPAY_API_KEY="your_key_from_dashboard"
# 1) Create payment — copy data.payment_reference from JSON
curl -sS -X POST "https://api.rushpay.cash/v1/payments/create" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${RUSHPAY_API_KEY}" \
-H "Idempotency-Key: my-order-123-unique" \
-d '{"amount":10.50,"description":"Order 123","callback_url":"https://yoursite.com/thank-you"}'
# 2) Widget session — paste PAYMENT_REF from step 1; copy data.widget_session_token
curl -sS -X POST "https://api.rushpay.cash/v1/payments/widget-session" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${RUSHPAY_API_KEY}" \
-d '{"payment_reference":"PAYMENT_REF"}'
<link rel="stylesheet" href="https://api.rushpay.cash/widget/payment-widget.css">
<div id="rushpay-payment-widget"></div>
<script>window.RUSHPAY_API_BASE = 'https://api.rushpay.cash/v1';</script>
<script src="https://api.rushpay.cash/widget/payment-widget.js"></script>
<script>
RushPay.init({
widgetSessionToken: 'PASTE_WIDGET_SESSION_TOKEN',
paymentReference: 'PASTE_PAYMENT_REFERENCE',
callbackUrl: 'https://yoursite.com/thank-you',
description: 'Order #123'
});
</script>
Load order: set RUSHPAY_API_BASE, then the widget script (sync), then RushPay.init. If you load the script with async, run init inside the script’s onload handler.
If checkout runs on another website and the widget fails with a browser network or CORS error, your storefront’s HTTPS origin must be allowed for the RushPay API—contact RushPay support with your checkout URL.
Inside the widget, the customer can use RushPay wallet (email → OTP → PIN → pay), Mobile money, Card (Visa/Mastercard/Verve via Paystack), or Gift card—see Endpoints for route names.
Production uses the first two rows + callbackUrl.
| Option | Type | Required | Description |
|---|---|---|---|
widgetSessionToken |
string | Yes (production) | From POST /v1/payments/widget-session on your server; browser sends X-RushPay-Widget-Session |
paymentReference |
string | Yes (production) | From POST /v1/payments/create; must match the session |
callbackUrl |
string | Yes | URL to redirect user after successful payment |
webhookUrl |
string | No | URL to receive webhook notifications (optional) |
description |
string | No | Payment description (default: "Payment via RushPay") |
widget-context using your widget session.Same as section B, with a minimal page shell. Inject tokens from your backend (PHP/Node/etc.) instead of hardcoding.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pay</title>
<link rel="stylesheet" href="https://api.rushpay.cash/widget/payment-widget.css">
</head>
<body>
<h1>Complete your payment</h1>
<div id="rushpay-payment-widget"></div>
<script>window.RUSHPAY_API_BASE = 'https://api.rushpay.cash/v1';</script>
<script src="https://api.rushpay.cash/widget/payment-widget.js"></script>
<script>
RushPay.init({
widgetSessionToken: 'TOKEN_FROM_YOUR_SERVER',
paymentReference: 'REF_FROM_POST_CREATE',
callbackUrl: 'https://yoursite.com/payment-success',
description: 'Order #12345'
});
</script>
</body>
</html>