cashup-pay-sdk
v1.0.0
Published
Drop-in "Pay with CashUp" button for any website. Let shoppers pay from their CashUp wallet at your checkout.
Maintainers
Readme
Pay with CashUp — JavaScript SDK
Let shoppers pay from their CashUp wallet at your online checkout. Add a "Pay with CashUp" button to any website in a few minutes — no dependencies, no build step.
<script src="https://app.cashup.io/sdk/v1/paywithcashup.js"></script>
<div data-cashup-button data-checkout-url="CHECKOUT_URL_FROM_YOUR_SERVER"></div>Table of contents
- How it works
- Quick start
- Installation
- Client SDK
- Server API
- Webhooks
- Test mode
- Merchant controls
- Security
- FAQ
How it works
"Pay with CashUp" is a wallet payment method, like STC Pay or Apple Pay. The shopper pays from money already in their CashUp wallet.
Shopper clicks "Pay with CashUp" on your site
→ your SERVER creates a checkout session (with your secret key)
→ the shopper is redirected to the CashUp hosted checkout
→ they sign in and confirm payment from their wallet
→ CashUp notifies your server via webhook + redirects the shopper back to you
→ CashUp settles the funds to your merchant accountA card never pays your store directly through CashUp — the shopper's wallet does. This keeps the integration simple and the money flow clean.
Quick start
1. Get your API keys
In the CashUp merchant dashboard go to Developers → API Keys and create a key. You get:
- a publishable key
pk_test_…/pk_live_…(safe for the browser) - a secret key
sk_test_…/sk_live_…(server-only — never expose it) - a webhook signing secret
whsec_…
Start with test keys — they run the full flow without moving real money.
2. Create a checkout session (server)
When the shopper checks out, call CashUp from your backend with your secret key:
curl -X POST https://api.cashup.io/api/v1/gateway/api/checkout-sessions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 150.00,
"currency": "SAR",
"orderRef": "ORDER-1234",
"successUrl": "https://your-store.com/success",
"cancelUrl": "https://your-store.com/cart"
}'Response:
{
"success": true,
"data": {
"id": "cks_…",
"sessionToken": "gcs_…",
"checkoutUrl": "https://app.cashup.io/checkout/gcs_…",
"status": "CREATED",
"mode": "TEST",
"amount": 150,
"currency": "SAR",
"expiresAt": "2026-06-19T12:30:00.000Z"
}
}3. Send the shopper to the checkout (client)
Drop the SDK in and point a button at the checkoutUrl:
<script src="https://app.cashup.io/sdk/v1/paywithcashup.js"></script>
<div data-cashup-button data-checkout-url="https://app.cashup.io/checkout/gcs_…"></div>That's it. The shopper pays, lands back on your successUrl, and your server
receives a checkout.completed webhook.
Installation
CDN / hosted (recommended) — always the latest v1:
<script src="https://app.cashup.io/sdk/v1/paywithcashup.js"></script>Self-host — copy paywithcashup.js into your assets and serve it yourself.
npm
npm install cashup-pay-sdkThe script attaches window.CashUpPay on import (it's a browser global, not an ES export):
import 'cashup-pay-sdk';
// window.CashUpPay is now availableOr reference the file directly: node_modules/cashup-pay-sdk/paywithcashup.js.
The script auto-initializes any [data-cashup-button] elements on the page
(including ones added before DOMContentLoaded).
Client SDK
Data-attribute API
Add a data-cashup-button element; the SDK renders a branded button inside it.
| Attribute | Description |
|-----------|-------------|
| data-checkout-url | The checkoutUrl your server returned. Clicking redirects here. |
| data-create-url | Alternative: an endpoint on your server the SDK POSTs to on click; it must return { checkoutUrl }. Use this to create the session lazily. |
| data-key | Your publishable key pk_…. Optional — used to theme the button and respect your dashboard on/off switch. |
| data-theme | light (default, solid blue) or dark (white, for dark pages). |
| data-label | Override the button text. |
| data-lang | en or ar. Defaults to the page <html lang>. |
<!-- create the session on click, from your own endpoint -->
<div data-cashup-button data-create-url="/api/cashup/create-session"></div>Programmatic API
// Render a button into an element
CashUpPay.renderButton({
selector: '#pay', // or element: domNode
checkoutUrl: 'https://app.cashup.io/checkout/gcs_…',
// OR: createUrl: '/api/cashup/create-session', createBody: { cart: 42 },
key: 'pk_test_…', // optional
theme: 'light', // 'light' | 'dark'
label: 'Pay with CashUp', // optional
lang: 'en', // 'en' | 'ar'
onError: (err) => console.error(err),
});
// Just redirect (if you render your own button)
CashUpPay.redirect('https://app.cashup.io/checkout/gcs_…');
// Override the API base (e.g. staging)
CashUpPay.configure({ apiBase: 'https://staging-api.cashup.io' });You can also set the API base on the script tag:
<script src=".../paywithcashup.js" data-api-base="https://staging-api.cashup.io"></script>Server API
Base URL: https://api.cashup.io/api/v1
All server-to-server calls authenticate with your secret key in the
Authorization: Bearer sk_… header. The key's mode (test/live) determines
the session mode automatically.
Create a checkout session
POST /gateway/api/checkout-sessions
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| amount | number | yes | In SAR, e.g. 150.00. |
| currency | string | no | Only SAR is supported (default). |
| orderRef | string | no | Your order id; echoed back and in webhooks. |
| description | string | no | Shown on the checkout. |
| successUrl | string (https) | yes | Where the shopper returns after paying. |
| cancelUrl | string (https) | yes | Where the shopper returns if they cancel. |
| webhookUrl | string (https) | no | Per-session override of your dashboard webhook URL. |
| metadata | object | no | Free-form data stored with the session. |
Send an Idempotency-Key: <unique> header to make retries safe — the same key
returns the same session instead of creating a duplicate.
On success the shopper is later redirected to:
successUrl?status=paid&session=<sessionToken>&ref=<orderRef>&txn=<id>
(cancelUrl?status=cancelled&… on cancel).
Retrieve a session
GET /gateway/api/checkout-sessions/:id — returns the session and its current
status (CREATED, PROCESSING, COMPLETED, CANCELLED, EXPIRED, …). Use
this to reconcile if you ever miss a webhook.
Webhooks
Configure a webhook URL in Developers → API Keys → (webhook), or pass
webhookUrl when creating a session. CashUp POSTs JSON events to it.
Events
| Event | When |
|-------|------|
| checkout.completed | Payment succeeded — fulfill the order. |
| checkout.cancelled | Shopper cancelled. |
| checkout.expired | Session expired unpaid. |
| checkout.failed | Payment failed. |
| refund.completed / refund.failed | (When refunds are enabled.) |
Payload:
{
"id": "evt_…",
"type": "checkout.completed",
"created": 1750000000,
"data": {
"sessionId": "cks_…",
"sessionToken": "gcs_…",
"status": "COMPLETED",
"amount": 150,
"currency": "SAR",
"orderRef": "ORDER-1234",
"transactionId": "…",
"paidAt": "2026-06-19T12:31:00.000Z"
}
}Verify the signature
Every delivery includes X-CashUp-Signature: t=<unix>,v1=<hex>. Compute
HMAC-SHA256(secret, "<t>.<rawBody>") with your whsec_… secret and compare in
constant time. Reject if it doesn't match or t is too old.
const crypto = require('crypto');
function verifyCashUpSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Respond 2xx quickly. CashUp retries failed deliveries with exponential backoff
and each event carries a unique id for idempotency.
Test mode
Use test keys (pk_test_… / sk_test_…). Test sessions complete the full
flow — button → hosted checkout → redirect back → webhook — without moving any
real money and never touch a real wallet. Switch to live keys when you're
ready.
Merchant controls
Merchants control the button from Developers in the dashboard:
- On/off switch for the payment method.
- Button theme (light/dark) and optional label / display name.
- Allowed website origins — restrict which domains may use your publishable key.
The SDK reads these (when you pass data-key) from the public config endpoint:
GET /gateway/config?key=pk_… → { valid, enabled, mode, merchant, button }
Security
- Never put your secret key (
sk_…) in client-side code. Only the publishable key (pk_…) is safe in the browser. - Always verify the webhook signature before acting on an event.
- Treat the webhook as the source of truth for fulfillment — not the browser
redirect (a shopper can navigate to the
successUrldirectly). - Restrict your publishable key with the allowed origins list.
FAQ
Do I need the SDK? No — it's a convenience. You can redirect to the
checkoutUrl yourself. The SDK just renders a consistent, accessible button.
Does the shopper need a CashUp account? They sign in (or register) with their phone during checkout; the payment comes from their CashUp wallet.
What currencies? SAR.
Where are the funds settled? To your CashUp merchant account, then paid out to your registered IBAN.
Built by CashUp · Report an issue · MIT licensed
