RushPay · REST API

Overview

RushPay REST API — accept payments, automate with webhooks, and integrate checkout with the payment widget.

What you can build with RushPay

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.

  • Accept payments — Create payment requests (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).
  • Automate your product — Register webhooks for payment lifecycle events; reconcile with GET /transactions and your merchant balance (GET /balances).
  • End-user wallets — For marketplaces or “show my RushPay balance” inside your app, use Customer session (POST /customer-auth/send-otpverify-otpGET /customer/balances).
  • Payouts — Send funds to a RushPay user with POST /payments/send where that fits your product.
  • Gift cards on your marketplace — Validate listing price against balance (POST /giftcards/validate-for-listing), then confirm a sold card (POST /giftcards/confirm-external-sale).
  • WordPress commerce — Use the official WooCommerce tab for gateway setup, webhooks, and troubleshooting.

Pick your path

Match your project to a starting point; each card links to the right tab or section on this page.

WooCommerce store

Install the RushPay plugin, paste your API Key, set the API base URL, and configure webhooks so orders update when checkout completes.

Go to:

Custom website or SaaS checkout

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.

Go to:

Mobile app

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.

Go to:

Marketplace or “show the user’s balance”

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.

Go to:

Also on this tab: Merchant balance vs customer balance further below.

Payouts / send money

Send funds to a RushPay user from your integration with POST /payments/send (see Payments in Endpoints).

Go to:

Gift cards resold on your marketplace

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.

Go to:

Automation and reporting

Authenticate with X-API-Key or OAuth (POST /auth/login), then pull GET /balances and GET /transactions for dashboards, reconciliation, or alerting.

Go to:

Getting Started

  1. Create a RushPay Account: You need an active RushPay user account to use the API.
  2. Generate API Credentials: Use My API Keys to create an API application and get API Key / OAuth credentials.
  3. Start Integrating: Use the endpoints below to integrate payments into your application.

WooCommerce stores: open the WooCommerce tab on this page for gateway setup, webhooks, troubleshooting, and how order data is sent to RushPay after checkout.

Merchant balance vs customer balance

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-otpPOST /customer-auth/verify-otpGET /customer/balances with Authorization: Bearer. Keep your API key server-side only.

Security essentials

  • Never expose API keys in client apps: Do not embed your 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 only: Call the API and your webhook URLs over https://. Do not send API keys, OAuth secrets, or customer tokens over plain HTTP.
  • TLS verification: In production, do not disable TLS certificate verification in HTTP clients (avoid CURLOPT_SSL_VERIFYPEER = false, rejectUnauthorized: false, etc.).
  • Trust server-side verification: Treat 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.
  • Webhooks: Use an HTTPS endpoint; require X-RushPay-Signature and verify HMAC with your secret (constant-time compare, e.g. PHP hash_equals); reject requests with missing or invalid signatures.
  • Logging: Do not log full API keys, OAuth secrets, webhook secrets, OTPs, PINs, or bearer tokens. Redact or truncate identifiers in application logs.
  • Browser / CSRF: If your site accepts callbacks or form posts that trigger money-moving actions, use CSRF tokens, same-site cookies, or server-only routes as appropriate.
  • Supply chain: Pin dependency versions, review updates, and keep .env and local secrets out of git and backups that are publicly accessible.
  • Embedded widget from your domain: The browser calls the RushPay API cross-origin (e.g. 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.

Rate Limits

  • Default: 60 requests per minute.
  • Some endpoints have specific limits.
  • Rate limit headers are included in all responses:
    • 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.

Response Format

Success Response

{
  "success": true,
  "message": "Operation successful",
  "data": { ... },
  "timestamp": "2025-01-15T10:30:00+00:00"
}

Error Response

{
  "success": false,
  "message": "Error message",
  "errors": ["Detailed error 1", "Detailed error 2"],
  "timestamp": "2025-01-15T10:30:00+00:00"
}

Paginated Response

{
  "success": true,
  "message": "Data retrieved successfully",
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "pages": 5
  },
  "timestamp": "2025-01-15T10:30:00+00:00"
}

HTTP Status Codes

  • 200 – Success
  • 201 – Created
  • 400 – Bad Request (validation error)
  • 401 – Unauthorized (invalid credentials)
  • 403 – Forbidden
  • 404 – Not Found
  • 405 – Method Not Allowed (e.g. using POST where only GET is defined, such as GET /v1/payments/widget-context)
  • 429 – Rate Limit Exceeded
  • 500 – Internal Server Error
  • 503 – Service Unavailable (e.g. misconfiguration such as missing CORS allowlist in production or a temporarily unavailable dependency)

Quick Start Examples

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.

Embeddable checkout (most websites)

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.

Using API Key

# 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}"

Using OAuth 2.0

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}"

Authentication Methods

RushPay API supports two authentication methods: API Key and OAuth 2.0.

API Key Authentication

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}"

Storing keys (.env and environment variables)

  • Create a .env file locally: RUSHPAY_API_KEY=your_key_from_dashboard. Add .env to .gitignore so it is never pushed.
  • In PHP, use 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.
  • In production, set the same variable in your host’s secret manager or dashboard (not only in files on disk).
  • Keep your API key secret.
  • Rotate keys regularly.
  • Never expose keys in client-side code or public repositories.

OAuth 2.0 (Client Credentials)

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.

Step 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\"
  }"

Export RUSHPAY_CLIENT_ID and RUSHPAY_CLIENT_SECRET from your .env or secret store before running the command.

Step 2: Use Access Token

Authorization: Bearer your_access_token_here
curl -X GET https://api.rushpay.cash/v1/balances \
  -H "Authorization: Bearer your_access_token_here"

Step 3: Refresh Token

curl -X POST https://api.rushpay.cash/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "def50200a1b2c3d4e5f6..."
  }'

Revoke Token

curl -X POST https://api.rushpay.cash/v1/auth/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "token_to_revoke"
  }'

Which Method Should I Use?

Use API Key if:

  • You are building a simple integration.
  • Requests come from your own backend server.
  • You don’t need token refresh.

Use OAuth 2.0 if:

  • You are building a production or multi-tenant app.
  • You need automatic token rotation and expiration.
  • You want stricter security controls.

Authentication

  • POST /auth/login – Get OAuth 2.0 access token.
  • POST /auth/refresh – Refresh access token.
  • POST /auth/revoke – Revoke access or refresh token.

Invoices

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).

Escrow

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).

  • Customer alerts – On checkout completion, guests may receive payment confirmations where your payment flow supports it. When an escrow deal becomes funded, the seller (beneficiary) may receive email/SMS if on file — in addition to merchant escrow.funded webhooks.
  • Auto-release – When you set release_at, RushPay processes due releases on a schedule (Africa/Accra). Contact RushPay support if you need help verifying scheduled settlement for your environment.
  • Webhook 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.
  • Webhook 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).
  • Webhook escrow.refunded – Merchant refunded buyer (same pattern: idempotency_key plus escrow_id, refund_transaction_id, buyer_user_id, amount, payment_reference, reference).

Payments

  • POST /payments/create – Create payment request (receive payment). Send a unique Idempotency-Key header per logical payment from your server.
  • POST /payments/widget-sessionServer-only: issue a short-lived token for the browser widget after create. Do not expose X-API-Key in JavaScript.
  • GET /payments/widget-contextGET 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).

Payment integration guidelines

  • Scope by application: Every payment belongs to the API application tied to your 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.
  • Server-authoritative amount: The amount debited is always the value stored when the payment was created (or updated by RushPay). Optional 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 and double-clicks: Send 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.
  • PIN verification: Wrong PIN attempts are limited; repeated failures may temporarily block further PIN checks for that user (error message returned by the API). Design your UI to show the message and avoid rapid retries.
  • Secrets: Keep API keys, OAuth client secrets, and webhook signing secrets in server-side configuration or a secrets manager only. Never embed them in mobile apps, browser JavaScript, or public repositories.

Gift cards — marketplace listing vs checkout vs public verify

These are different flows. Do not confuse them.

  • POST /giftcards/validate-for-listingServer-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-saleServer-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-widgetCheckout widget only. Authenticate with X-RushPay-Widget-Session (recommended) or merchant X-API-Key; must match the payment’s payment_reference.
  • POST /public/giftcards/verifyPublic 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.

POST /payments/update-merchant

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"}}}'

Transactions

  • GET /transactions – List transactions with pagination and filters.
  • GET /transactions/{id} – Get single transaction details.
  • GET /transactions/search – Search by reference or other fields.

Balances

  • GET /balances – Get account balance and available balance for the RushPay user linked to your API application (merchant / API owner). Optional ?include_wallets=1.

Customer session (end-user balance)

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"

Webhooks

  • POST /webhooks/register – Register webhook URL.
  • GET /webhooks – List registered webhooks.
  • DELETE /webhooks – Delete a webhook.

Accounts

  • 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 Overview

Webhooks let RushPay notify your application in real-time when important events happen, such as payments completing or failing.

Register Webhook URL

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"
  }'

Verify Signatures

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);
}

Common Events

  • 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.

Best Practices

  • Verify signatures for every request using your configured webhook secret (never commit the secret to source control).
  • Return a 2xx status quickly, then process asynchronously.
  • Use X-RushPay-Event-Id (or equivalent) for idempotency so duplicate deliveries do not double-fulfill.
  • Log deliveries for debugging; failures to send email or side jobs must not roll back a payment that already succeeded in RushPay.
  • Reconcile before fulfilling: A 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.

Headers

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.

Example: 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"
}

Retry behaviour

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.

RushPay for WooCommerce

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).

Setup

  1. Install and activate RushPay for WooCommerce.
  2. WooCommerce → Settings → Payments → RushPay: enable the gateway.
  3. Paste your API Key and set API Base URL (default https://api.rushpay.cash).
  4. Copy the Webhook URL into your RushPay API application settings and set the Webhook Secret in WooCommerce for signature verification.

Checkout behavior

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).

Merchant context in the RushPay dashboard

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" }
      ]
    }
  }
}

Troubleshooting

  • API errors on checkout – Confirm API Base URL and API key; ensure the server allows outbound HTTPS to the API host.
  • Order not showing in the RushPay dashboard – Checkout must complete the wallet or mobile-money flow that saves the payment reference and runs update-merchant; check WooCommerce logs for RushPay update-merchant failed.
  • Webhooks not updating orders – Match the webhook URL and secret between RushPay and WooCommerce; verify X-RushPay-Signature with your secret.
  • Webhook after order cancelled – A 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.

Code Examples

Complete, production-ready code samples for all RushPay API features. Simply insert your API key and run.

Note: Server-side examples load 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.

1. Simple Balance Check (API Key)

<?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).
}

2. Get Balance with OAuth 2.0

<?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";
}

3. List Transactions

<?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";
}

4. Create Payment Request

<?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";
}

5. Complete Payment Page

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>

6. Giftcard/Voucher Support

<?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";
}

7. Send Payment to RushPay User

<?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";
}

8. Verify Payment Status

<?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";
}

RushPay Payment Widget

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.

Copy-paste integration (production)

  1. Shopper clicks pay; your backend knows the amount and order id.
  2. Backend: POST /v1/payments/create → save data.payment_reference.
  3. Backend: POST /v1/payments/widget-session with that reference → save data.widget_session_token.
  4. Render your checkout page with the HTML below, replacing placeholders with those two strings (from session, DB, or template variables).

A. Server (run in terminal or equivalent in PHP/Node/Python)

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"}'

B. Browser page (placeholders only—inject from your server)

<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.

Configuration options

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")

Payment Flow

  1. Amount: The amount is loaded from the server via widget-context using your widget session.
  2. Email Verification: User enters RushPay email address
  3. Account Check: Widget verifies account exists via API
  4. OTP Sent: One-time password sent to user's email and SMS (if they have a phone on file)
  5. OTP Verification: User enters 6-digit OTP code
  6. Balance Display: User's RushPay balance is displayed
  7. PIN Entry: User enters 4-digit account PIN to confirm payment
  8. Payment Processing: User clicks "Confirm Payment" (widget sends PIN with request)
  9. Success: Payment processed and user redirected to callback URL

Full HTML page template

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>