Ummah
Payment Gateway API

A robust, developer-friendly REST API for integrating payments into your application. Built for e-commerce, subscriptions, and enterprise billing workflows.

Platform Integrations
WooCommerceOpenCart MagentoPHP PythonNode.js JavaiOS Android

Quickstart — Accept a Payment in 5 Minutes

Copy-paste this example into your project. Uses Node.js but the same flow applies to any language.

Install the SDK
npm install @ummah/payments
Initialize the client
Setup
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);
# macOS/Linux
export UMMAH_SECRET_KEY=sk_test_your_key_here

# Windows PowerShell
$env:UMMAH_SECRET_KEY="sk_test_your_key_here"
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);
import os
import ummah

client = ummah.Client(api_key=os.environ['UMMAH_SECRET_KEY'])
Create a purchase
Request
const purchase = await ummah.purchases.create({
  brand_id: '409eb80e-3782-4b1d-afa8-b779759266a5',
  client: { email: 'user@example.com' },
  purchase: {
    products: [{ name: 'T-Shirt', price: 5000, quantity: 1 }],
    currency: 'USD',
  },
  success_redirect: 'https://yourapp.com/success',
  failure_redirect: 'https://yourapp.com/failure',
});

console.log(purchase.checkout_url);
curl -X POST "https://gate.ummah.com/api/v1/purchases/" \
  -H "Authorization: Bearer $UMMAH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
    "client": { "email": "user@example.com" },
    "purchase": {
      "products": [{ "name": "T-Shirt", "price": 5000, "quantity": 1 }],
      "currency": "USD"
    },
    "success_redirect": "https://yourapp.com/success",
    "failure_redirect": "https://yourapp.com/failure"
  }'
const purchase = await ummah.purchases.create({
  brand_id: '409eb80e-3782-4b1d-afa8-b779759266a5',
  client: { email: 'user@example.com' },
  purchase: {
    products: [{ name: 'T-Shirt', price: 5000, quantity: 1 }],
    currency: 'USD',
  },
  success_redirect: 'https://yourapp.com/success',
  failure_redirect: 'https://yourapp.com/failure',
});

console.log(purchase.checkout_url);
// → https://pay.ummah.com/checkout/pur_abc123
import requests, os

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/',
    headers={'Authorization': f'Bearer {os.environ["UMMAH_SECRET_KEY"]}'},
    json={
        'brand_id': '409eb80e-3782-4b1d-afa8-b779759266a5',
        'client': {'email': 'user@example.com'},
        'purchase': {
            'products': [{'name': 'T-Shirt', 'price': 5000, 'quantity': 1}],
            'currency': 'USD',
        },
        'success_redirect': 'https://yourapp.com/success',
        'failure_redirect': 'https://yourapp.com/failure',
    }
)
purchase = response.json()
print(purchase['checkout_url'])
Run it locally (copy‑paste)
# macOS/Linux export UMMAH_SECRET_KEY=sk_test_your_key_here node quickstart.js # Windows PowerShell $env:UMMAH_SECRET_KEY="sk_test_your_key_here" node quickstart.js
quickstart.js
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const purchase = await ummah.purchases.create({
  brand_id: '409eb80e-3782-4b1d-afa8-b779759266a5',
  client: { email: 'user@example.com' },
  purchase: { products: [{ name: 'T-Shirt', price: 5000, quantity: 1 }], currency: 'USD' },
  success_redirect: 'https://yourapp.com/success',
  failure_redirect: 'https://yourapp.com/failure',
});

console.log('Open checkout:', purchase.checkout_url);
Redirect your customer to checkout
Send the customer to purchase.checkout_url — a PCI-compliant hosted page. They enter their card details there; your server never handles raw card data.
Listen for the webhook
When payment completes, Ummah POSTs a purchase.paid event to your callback URL. Verify the X-Signature header and fulfill the order. See the Webhook Verification Guide.
Confirm final status
Treat GET /purchases/{id}/ as source of truth (use the purchase ID from your database or webhook payload).

Why Ummah

Ummah is purpose-built for businesses that require ethical, Shariah-aligned payment processing — without sacrificing the modern developer experience you expect from leading gateways.

FeatureUmmahStripe
Halal-compliant processing — no interest, no prohibited industries✓ Built-in✕ Not applicable
Shariah-aligned profit-sharing — revenue model reviewed by scholars✓ Yes✕ Conventional interest model
Built-in billing templates — reusable subscription plans✓ Native∼ Requires Stripe Billing add-on
Transparent, flat-rate pricing — one rate, no hidden fees✓ Yes∼ Tiered, plus add-on fees
Marketplace-ready — split payments between sellers✓ Roadmap✓ Stripe Connect
Hosted checkout (PCI DSS Level 1)✓ Yes✓ Yes
Multi-currency support✓ Yes✓ Yes
RSA-SHA256 signed webhooks✓ Yes✓ Yes
Idempotency keys — safe request retries✓ Yes✓ Yes
Dedicated Muslim-majority market focus✓ Core mission✕ Not a focus
Roadmap note: Marketplace split payments are on our public roadmap for 2025. Contact us if this is a hard requirement — early access is available for select partners.

Authentication

All API requests must include your secret key in the Authorization header using Bearer scheme.

Authorization: Bearer YOUR_SECRET_KEY
Test Mode — Use your sk_test_* key to make sandbox requests. No real payments are processed. Switch to sk_live_* for production.

API Keys

Retrieve your API keys from the dashboard under Settings → API Keys. Keep your secret key confidential — never expose it in client-side code.

Core Objects

The API is built around a small set of objects. If you understand these, the rest of the endpoints feel predictable.

erDiagram
    BRAND ||--o{ PURCHASE : "owns"
    CLIENT ||--o{ PURCHASE : "has"
    CLIENT ||--o{ RECURRING_TOKEN : "stores"
    BILLING_TEMPLATE ||--o{ SUBSCRIBER : "has"
    SUBSCRIBER }o--|| CLIENT : "references"
    WEBHOOK ||--o{ DELIVERY : "sends"
🛒Purchase

The core transaction object. Holds products, payment status, checkout URL, and client info.

👤Client

A customer record with contact details and optional saved payment methods (recurring tokens).

📋Billing Template (Plan)

A reusable invoice or subscription plan. Used to create invoices or manage recurring billing.

🔄Subscriber

A client enrolled on a billing template. Tracks recurring status and next billing date.

🔔Webhook

Your callback endpoint configuration. Used to receive signed events like purchase.paid.

Start here (recommended mental model)

Payments
Create a Purchase → redirect to checkout_url → verify the webhook → retrieve the Purchase for final status.
Subscriptions
Create a Billing TemplateSubscribe a client → pause/resume via the subscriber endpoints (API route name: clients).
One-click checkout
First purchase with force_recurring: true → store recurring_token → later create a new purchase using the saved method.

Core Object Shapes

These examples show the typical fields you’ll see. Use them as a guide for what to log and what to store in your database.

Purchase
{
  "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "status": "created",
  "checkout_url": "https://pay.ummah.com/checkout/d6c9...",
  "invoice_url": "https://pay.ummah.com/invoice/d6c9...",
  "client": {
    "id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
    "email": "customer@example.com",
    "full_name": "John Doe"
  },
  "purchase": {
    "total_amount": 2999,
    "currency": "USD"
  },
  "recurring_token": "tok-a1b2c3d4-e5f6-7890",
  "created_on": 1700000000
}
Client
{
  "id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
  "email": "client@example.com",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "country": "US",
  "type": "personal",
  "created_on": 1700000000
}
Billing Template (Plan)
{
  "id": "bt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
  "is_subscription": true,
  "subscription_period": 1,
  "subscription_period_units": "months",
  "purchase": { "total_amount": 999, "currency": "USD" },
  "created_on": 1700000000
}
Subscriber (API: template client)
{
  "id": "btc-a1b2c3d4-e5f6-7890",
  "client": { "email": "subscriber@example.com" },
  "is_subscription": true,
  "status": "active",
  "next_billing_on": 1703000000,
  "created_on": 1700000000
}
Webhook delivery (payload)
{
  "id": "evt-a1b2c3d4-e5f6-7890",
  "type": "purchase.paid",
  "created_on": 1700000000,
  "object": {
    "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
    "status": "paid",
    "purchase": { "total_amount": 2999, "currency": "USD" }
  }
}
What to store — At minimum store purchase.id, status, and any business metadata you need. If you use one-click checkout, store the recurring_token securely with a reference to your user/customer.

Test Mode & Sandbox

Use your sk_test_* key to test the full API without processing real payments. All endpoints behave identically in test and live mode.

Test Card Numbers

Card NumberResultCVCExpiry
4242 4242 4242 4242SuccessAny 3 digitsAny future date
4000 0000 0000 0002DeclinedAny 3 digitsAny future date
4000 0000 0000 32203D Secure requiredAny 3 digitsAny future date
Sandbox only — Test cards only work with sk_test_* keys. Never use real card numbers in test mode.

Webhook Testing Tips

Use tools like ngrok to expose your local server for webhook testing. Set your callback URL to the ngrok tunnel URL during development.

Replay events locally — You can copy an example payload and replay it to your webhook handler to test signature verification + idempotency. See the webhook replay tool in Security Best Practices.

Go live — Sandbox → Production checklist

This checklist helps you ship safely. Most production incidents come from keys, webhooks, retries, and idempotency.

1) Keys & environments

Use test keys in development
Use sk_test_* locally and in staging. Never embed keys in client-side code.
Switch to live keys for production
Rotate to sk_live_* only in your production secrets manager (env vars). Treat keys like passwords.

2) Webhooks (required)

Register a production webhook URL
Create a webhook with POST /webhooks/ pointing to your production HTTPS endpoint.
Verify signatures on every delivery
Follow Webhook Verification Guide. Use the raw request body and constant-time comparison.
Add replay protection + idempotency
Enforce timestamp tolerance and store each event.id (or object.id) for 24h to ignore duplicates.

3) Retries & idempotency (API calls)

Set timeouts
Use client timeouts (e.g. 10–30s) so your worker doesn’t hang.
Retry safely
Retry 429 and 5xx with exponential backoff. For POST, reuse the same Idempotency-Key across retries.
Log the right identifiers
Store purchase.id, final status, and webhook event.id in your DB for support/debugging.
Don’t trust redirects as source of truth — Always use webhooks + GET /purchases/{id}/ to confirm the final status.
Accept Payments

Accept Payments

Create purchases, redirect customers to checkout, and manage the full payment lifecycle — authorization, capture, refunds, and recurring charges.

Payment Flow

Every purchase follows this lifecycle. Use skip_capture: true to place funds on hold first and capture later.

stateDiagram-v2
    [*] --> Created: POST /purchases/
    Created --> Hold: Checkout success (skip_capture=true)
    Created --> Paid: Checkout success
    Created --> Cancelled: POST /cancel/
    Hold --> Paid: POST /capture/
    Hold --> Released: POST /release/
    Paid --> Refunded: POST /refund/

Status → next action (practical)

StatusWhat it meansWhat you do
createdPurchase created; customer has not completed checkout yet.Redirect to checkout_url. Optionally allow cancel.
holdFunds are held. Nothing has been captured yet.Capture with POST /capture/ or release with POST /release/.
pending_captureCapture has been requested and is processing.Wait for the status to transition (webhook/poll GET /purchases/{id}/).
pending_releaseRelease has been requested and is processing.Wait for the status to transition (webhook/poll GET /purchases/{id}/).
paidFunds are captured/settled.Fulfill the order. Refund with POST /refund/ if needed.
cancelledPurchase was cancelled before payment completed.Stop the flow; create a new purchase if the customer retries.
releasedHeld funds were released.Stop the flow; create a new purchase if needed.
refundedFunds were returned to the customer (full/partial).Update your records; handle refund webhooks if you rely on them.
Recommended pattern — Use webhooks for the instant signal (purchase.paid), but always treat GET /purchases/{id}/ as the source of truth for the final status.

Create a Purchase

POST/api/v1/purchases/

Register a new payment and receive a checkout_url to redirect your customer to. After payment, the gateway redirects back to your success_redirect / failure_redirect.

ParameterDescription
brand_idREQUIREDuuidID of the brand to create this Purchase for.
client.emailREQUIREDstringCustomer email address.
purchase.productsREQUIREDarrayProducts array. Each item: {name, price, quantity}.
purchase.currencyOPTIONALstring3-letter ISO currency code. Defaults to brand currency.
success_redirectOPTIONALstringURL to redirect to after successful payment.
failure_redirectOPTIONALstringURL to redirect to after payment failure.
success_callbackOPTIONALstringURL to POST a signed payment notification to.
skip_captureOPTIONALbooleanPlace funds on hold; capture later via POST /capture/.
force_recurringOPTIONALbooleanSave payment method for future recurring charges.
send_receiptOPTIONALbooleanEmail a receipt to the client after payment.
Request
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const purchase = await ummah.purchases.create({
  brand_id: '409eb80e-3782-4b1d-afa8-b779759266a5',
  client: { email: 'customer@example.com' },
  purchase: {
    products: [{ name: 'T-Shirt', price: 2999, quantity: 1 }],
    currency: 'USD',
  },
  success_redirect: 'https://myshop.com/success',
  failure_redirect: 'https://myshop.com/failed',
});

console.log(purchase.checkout_url);
curl -X POST "https://gate.ummah.com/api/v1/purchases/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
    "client": {
        "email": "customer@example.com"
    },
    "purchase": {
        "products": [
            {
                "name": "T-Shirt",
                "price": 2999,
                "quantity": 1
            }
        ],
        "currency": "USD"
    },
    "success_redirect": "https://myshop.com/success",
    "failure_redirect": "https://myshop.com/failed"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
  "client": {
    "email": "customer@example.com"
  },
  "purchase": {
    "products": [
      {
        "name": "T-Shirt",
        "price": 2999,
        "quantity": 1
      }
    ],
    "currency": "USD"
  },
  "success_redirect": "https://myshop.com/success",
  "failure_redirect": "https://myshop.com/failed"
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
    'brand_id': '409eb80e-3782-4b1d-afa8-b779759266a5',
    'client': {'email': 'customer@example.com'},
    'purchase': {
        'products': [{'name': 'T-Shirt', 'price': 2999, 'quantity': 1}],
        'currency': 'USD',
    },
    'success_redirect': 'https://myshop.com/success',
    'failure_redirect': 'https://myshop.com/failed',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/',
    headers=headers,
    json=data
)

print(response.json())
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
      "client": {
        "email": "customer@example.com"
      },
      "purchase": {
        "products": [
          {
            "name": "T-Shirt",
            "price": 2999,
            "quantity": 1
          }
        ],
        "currency": "USD"
      },
      "success_redirect": "https://myshop.com/success",
      "failure_redirect": "https://myshop.com/failed"
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "status": "created",
  "checkout_url": "https://pay.ummah.com/checkout/d6c9...",
  "invoice_url": "https://pay.ummah.com/invoice/d6c9...",
  "client": { "email": "customer@example.com", "full_name": "John Doe" },
  "purchase": { "total_amount": 2999, "currency": "USD" },
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Retrieve a Purchase

GET/api/v1/purchases/{id}/

Retrieve the full details of a purchase by its ID, including current status, payment data, and client info.

Request
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const purchase = await ummah.purchases.retrieve('{id}');
console.log(purchase.status);
curl -X GET "https://gate.ummah.com/api/v1/purchases/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/purchases/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/purchases/{id}/',
    headers=headers
)

print(response.json())
Response
{
  "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "status": "paid",
  "checkout_url": "https://pay.ummah.com/checkout/d6c9...",
  "purchase": { "total_amount": 2999, "currency": "USD" },
  "payment": { "method": "card", "is_recurring_token": false },
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Cancel a Purchase

POST/api/v1/purchases/{id}/cancel/

Cancel a purchase that hasn’t been completed yet. Use this to guarantee it can’t be paid later.

Request
curl -X POST "https://gate.ummah.com/api/v1/purchases/{id}/cancel/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/{id}/cancel/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/cancel/',
    headers=headers
)

print(response.json())
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/cancel/',
    headers=headers
)

print(response.json())
Response
{"status": "cancelled"}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Release Held Funds

POST/api/v1/purchases/{id}/release/

Release funds reserved for a purchase on hold (status == hold). Used with skip_capture: true (API route name: release).

Request
curl -X POST "https://gate.ummah.com/api/v1/purchases/{id}/release/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/{id}/release/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/release/',
    headers=headers
)

print(response.json())
Response
{"status": "released"}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Capture a Purchase

POST/api/v1/purchases/{id}/capture/

Capture funds reserved for a purchase on hold (status == hold). Optionally specify a partial amount in the smallest currency unit.

ParameterDescription
amountOPTIONALintegerAmount to capture in smallest currency unit (e.g. cents). Defaults to full held amount.
Request
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const purchase = await ummah.purchases.capture('{id}', { amount: 2999 });
console.log(purchase.status);
curl -X POST "https://gate.ummah.com/api/v1/purchases/{id}/capture/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2999
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "amount": 2999
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/{id}/capture/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {'amount': 2999}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/capture/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "status": "paid",
  "checkout_url": "https://pay.ummah.com/checkout/d6c9...",
  "purchase": { "total_amount": 2999, "currency": "USD" },
  "payment": { "method": "card", "is_recurring_token": false },
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Refund a Purchase

POST/api/v1/purchases/{id}/refund/

Refund a paid purchase in full or partially. Optionally pass an internal reference for your records.

ParameterDescription
amountOPTIONALintegerAmount to refund in smallest currency unit. Defaults to full amount.
referenceOPTIONALstringInternal refund reference for your records.
Request
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const refund = await ummah.purchases.refund('{id}', {
  amount: 1500,
  reference: 'REFUND-2024-001',
});

console.log(refund);
curl -X POST "https://gate.ummah.com/api/v1/purchases/{id}/refund/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1500,
    "reference": "REFUND-2024-001"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "amount": 1500,
  "reference": "REFUND-2024-001"
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/{id}/refund/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
    'amount': 1500,
    'reference': 'REFUND-2024-001',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/refund/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "pay-r1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "refunded",
  "amount": 1500,
  "currency": "USD",
  "purchase": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "method": "card",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Confirm Offline Payment

POST/api/v1/purchases/{id}/mark_as_paid/

Manually mark a purchase as paid. Useful for cash/offline payments recorded in the system.

ParameterDescription
paid_onOPTIONALtimestampUnix timestamp of when payment was received. Defaults to now.
Request
curl -X POST "https://gate.ummah.com/api/v1/purchases/{id}/mark_as_paid/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "paid_on": 1700000000
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "paid_on": 1700000000
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/{id}/mark_as_paid/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {'paid_on': 1700000000}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/mark_as_paid/',
    headers=headers,
    json=data
)

print(response.json())
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "paid_on": 1700000000
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/mark_as_paid/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "status": "paid",
  "checkout_url": "https://pay.ummah.com/checkout/d6c9...",
  "purchase": { "total_amount": 2999, "currency": "USD" },
  "payment": { "method": "card", "is_recurring_token": false },
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Resend Invoice Email

POST/api/v1/purchases/{id}/resend_invoice/

Re-sends the invoice email for this purchase to the client.

Request
curl -X POST "https://gate.ummah.com/api/v1/purchases/{id}/resend_invoice/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/{id}/resend_invoice/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/resend_invoice/',
    headers=headers
)

print(response.json())
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/resend_invoice/',
    headers=headers
)

print(response.json())
Response
{"status": "sent"}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Detach Payment Method

POST/api/v1/purchases/{id}/delete_recurring_token/

Detach the saved payment method used for future charges (API route name: delete_recurring_token).

Request
curl -X POST "https://gate.ummah.com/api/v1/purchases/{id}/delete_recurring_token/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/{id}/delete_recurring_token/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/purchases/{id}/delete_recurring_token/',
    headers=headers
)

print(response.json())
Response
{"status": "deleted"}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

List Payment Methods

GET/api/v1/payment_methods/

Get the list of payment methods available for a brand/currency. Requires brand_id and currency query parameters.

Request
curl -X GET "https://gate.ummah.com/api/v1/payment_methods/?brand_id={brand_id}&currency=USD" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/payment_methods/?brand_id={brand_id}¤cy=USD',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/payment_methods/?brand_id={brand_id}¤cy=USD',
    headers=headers
)

print(response.json())
Response
{
  "available_payment_methods": ["visa", "mastercard"],
  "by_country": { "any": ["card"] },
  "country_names": { "any": "Other" },
  "names": { "visa": "Visa", "mastercard": "Mastercard" },
  "logos": { "visa": "/static/images/icon-visa.svg", "mastercard": "/static/images/icon-mastercard.svg" },
  "card_methods": ["visa", "mastercard"]
}

List Payout Methods

GET/api/v1/payout_methods/

Get the list of payout methods available for a brand/currency. Requires brand_id and currency query parameters.

Request
curl -X GET "https://gate.ummah.com/api/v1/payout_methods/?brand_id={brand_id}&currency=USD" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/payout_methods/?brand_id={brand_id}¤cy=USD',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/payout_methods/?brand_id={brand_id}¤cy=USD',
    headers=headers
)

print(response.json())
Response
{
  "available_payout_methods": ["visa", "mastercard"],
  "names": { "visa": "Visa", "mastercard": "Mastercard" },
  "logos": { "visa": "/static/images/icon-visa.svg", "mastercard": "/static/images/icon-mastercard.svg" },
  "card_methods": ["visa", "mastercard"]
}
Guides

Developer Goals

Start with an outcome, follow a short flow, then jump to the exact endpoints when you need the details.

Get Paid in 5 Minutes

The shortest path to production: create a purchase, redirect to checkout, then verify the webhook and confirm final status.

Step 1 — Create purchase
Call POST /purchases/ to get a checkout_url.
Step 2 — Redirect to checkout
Send your customer to checkout_url.
Step 3 — Verify webhook
Verify X-Signature (see Webhook Verification Guide) and dedupe events.
Step 4 — Confirm final status
Fetch the purchase from GET /purchases/{id}/ as source of truth.

Accept One-Time Payments

Use hosted checkout to collect payment details and complete PCI-compliant payments without handling raw card data.

Create purchase
Use POST /purchases/ with products and redirects.
Handle success/failure
Use your redirect pages for UX; use webhooks for reliability.
Tip — If you retry a create request, pass the same Idempotency-Key to avoid duplicates (see Error Codes).

Pre‑Authorization & Capture

Authorize funds first, then capture later (hotels, marketplaces, delayed fulfillment).

Authorize
Create a purchase with skip_capture: true via POST /purchases/.
Capture
Void authorization
If cancelled, call POST /purchases/{id}/release/.

Refunds

Refund fully or partially after a purchase is paid.

Confirm paid status
Refund
Call POST /purchases/{id}/refund/ with optional amount and reference.

Subscriptions

Create a plan (billing template), subscribe a client, then pause/resume when needed.

Create plan
Use POST /billing_templates/ with is_subscription: true.
Pause / resume

Invoicing

Send one-time invoices to clients, or generate invoices from templates.

Send invoice
Use POST /billing/ for one-time invoices.

Customers & Saved Cards

Save a payment method on first checkout, then charge later without redirect (“one-click checkout”).

Save on first payment
Create purchase with force_recurring: true via POST /purchases/.
Charge later
Create a new purchase using the stored recurring_token to support one-click checkout.
Detach payment method
Allow customers to remove saved methods via POST /purchases/{id}/delete_recurring_token/.
See alsoCommon Patterns for full recipes.

Reporting & Payouts

Track balances and generate statements for accounting.

Generate statements
Use POST /company_statements/ then poll with GET /company_statements/{id}/ for download_url.

Webhooks & Security

Webhooks give you reliable payment status updates. Always verify signatures and prevent replay.

Create webhook
Register via POST /webhooks/.
Fetch public key
Get it via GET /public_key/.
Verify + dedupe
Subscriptions

Recurring Billing

Create plans (billing templates), add subscribers, and let the gateway handle automatic renewal charges on your schedule.

Subscription Lifecycle

Subscribers progress through these states. Use the subscriber endpoints (API route name: clients) to pause or resume billing.

stateDiagram-v2
    [*] --> Plan: POST /billing_templates/
    Plan --> Subscriber: POST /billing_templates/{id}/add_subscriber/
    Subscriber --> Active: First payment
    Active --> Renewal: Billing cycle
    Renewal --> Active: Payment success
    Renewal --> Failed: Payment failure
    Active --> Paused: PATCH status=subscription_paused
    Paused --> Active: PATCH status=active
Tip — For trials, subscribe the client then set status: "subscription_paused" until the trial ends. Resume with PATCH /billing_templates/{id}/clients/{client_id}/.

Create Billing Template

POST/api/v1/billing_templates/

Create a reusable billing template (Plan) for one-time invoices or recurring subscriptions. For subscriptions, set is_subscription: true and provide subscription_period + subscription_period_units.

ParameterDescription
purchaseREQUIREDobjectPurchaseDetails object: products, currency, etc.
is_subscriptionREQUIREDbooleantrue for subscription, false for one-time invoicing.
brand_idREQUIREDuuidID of the brand for this template.
subscription_periodOPTIONALintegerHow often to bill (required when is_subscription: true). Example: 1.
subscription_period_unitsOPTIONALstringUnits for subscription_period (required when is_subscription: true): days, weeks, months, years.
number_of_billing_cyclesOPTIONALintegerLimit billing cycles per client. 0 = unlimited.
Request
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const template = await ummah.billingTemplates.create({
  brand_id: '409eb80e-...',
  is_subscription: true,
  purchase: {
    products: [{ name: 'Monthly Plan', price: 999 }],
    currency: 'USD',
  },
  subscription_period: 1,
  subscription_period_units: 'months',
});

console.log(template.id);
curl -X POST "https://gate.ummah.com/api/v1/billing_templates/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "brand_id": "409eb80e-...",
    "is_subscription": true,
    "purchase": {
        "products": [
            {
                "name": "Monthly Plan",
                "price": 999
            }
        ],
        "currency": "USD"
    },
    "subscription_period": 1,
    "subscription_period_units": "months"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "brand_id": "409eb80e-...",
  "is_subscription": true,
  "purchase": {
    "products": [
      {
        "name": "Monthly Plan",
        "price": 999
      }
    ],
    "currency": "USD"
  },
  "subscription_period": 1,
  "subscription_period_units": "months"
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/billing_templates/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "brand_id": "409eb80e-...",
      "is_subscription": True,
      "purchase": {
        "products": [
          {
            "name": "Monthly Plan",
            "price": 999
          }
        ],
        "currency": "USD"
      },
      "subscription_period": 1,
      "subscription_period_units": "months"
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/billing_templates/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "bt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "is_subscription": true,
  "purchase": { "currency": "USD", "total_amount": 999 },
  "subscription_period": 1,
  "subscription_period_units": "months",
  "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

List Billing Templates

GET/api/v1/billing_templates/

List all billing templates for your account.

Request
curl -X GET "https://gate.ummah.com/api/v1/billing_templates/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/billing_templates/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/billing_templates/',
    headers=headers
)

print(response.json())
Response
{
  "results": [{
    "id": "bt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "is_subscription": true,
    "purchase": { "currency": "USD", "total_amount": 999 },
    "subscription_period": 1,
    "subscription_period_units": "months",
    "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
    "created_on": 1700000000
  }],
  "next": null,
  "previous": null
}

Retrieve Billing Template

GET/api/v1/billing_templates/{id}/

Retrieve a billing template by its ID.

Request
curl -X GET "https://gate.ummah.com/api/v1/billing_templates/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/',
    headers=headers
)

print(response.json())
Response
{
  "id": "bt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "is_subscription": true,
  "purchase": { "currency": "USD", "total_amount": 999 },
  "subscription_period": 1,
  "subscription_period_units": "months",
  "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Update Billing Template

PUT/api/v1/billing_templates/{id}/

Replace all fields of a billing template.

ParameterDescription
purchaseREQUIREDobjectUpdated PurchaseDetails object.
is_subscriptionREQUIREDbooleanSubscription mode flag.
Request
curl -X PUT "https://gate.ummah.com/api/v1/billing_templates/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "purchase": {
        "products": [
            {
                "name": "Updated Plan",
                "price": 1299
            }
        ],
        "currency": "USD"
    },
    "is_subscription": true
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "purchase": {
    "products": [
      {
        "name": "Updated Plan",
        "price": 1299
      }
    ],
    "currency": "USD"
  },
  "is_subscription": true
};

const { data: result } = await axios.put(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "purchase": {
        "products": [
          {
            "name": "Updated Plan",
            "price": 1299
          }
        ],
        "currency": "USD"
      },
      "is_subscription": True
    }

response = requests.put(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "bt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "is_subscription": true,
  "purchase": { "currency": "USD", "total_amount": 999 },
  "subscription_period": 1,
  "subscription_period_units": "months",
  "brand_id": "409eb80e-3782-4b1d-afa8-b779759266a5",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Delete Billing Template

DELETE/api/v1/billing_templates/{id}/

Delete a billing template by its ID. This does not cancel active subscriptions.

Request
curl -X DELETE "https://gate.ummah.com/api/v1/billing_templates/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.delete(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.delete(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/',
    headers=headers
)

print(response.status_code)
Response
(empty — 204 No Content)
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Subscribe

POST/api/v1/billing_templates/{id}/add_subscriber/

Add a client to a subscription billing template (API route name: add_subscriber). Depending on the template settings, the response may include a purchase you must redirect the customer to immediately, or purchase: null if billing starts later (trial/period-end charging).

ParameterDescription
client_idREQUIREDuuidID of an existing Client to subscribe.
Request
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const subscription = await ummah.billingTemplates.subscribe('{id}', {
  client_id: 'b79d3df6-2f69-4426-acee-eda049d83e18',
});

console.log(subscription.status);
curl -X POST "https://gate.ummah.com/api/v1/billing_templates/{id}/add_subscriber/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/add_subscriber/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/add_subscriber/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "billing_template_client": {
    "id": "btc-a1b2c3d4-e5f6-7890",
    "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18",
    "status": "pending",
    "created_on": 1700000000
  },
  "purchase": null
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

List Subscribers

GET/api/v1/billing_templates/{id}/clients/

List all subscribers for this billing template (Plan) (API route name: clients).

Request
curl -X GET "https://gate.ummah.com/api/v1/billing_templates/{id}/clients/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/clients/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/clients/',
    headers=headers
)

print(response.json())
Response
{
  "results": [{
    "id": "btc-a1b2c3d4-e5f6-7890",
    "client": { "email": "subscriber@example.com" },
    "is_subscription": true,
    "status": "active",
    "next_billing_on": 1703000000,
    "created_on": 1700000000
  }],
  "next": null,
  "previous": null
}

Get Subscriber

GET/api/v1/billing_templates/{id}/clients/{client_id}/

Retrieve a subscriber for this billing template (Plan) (API route name: clients).

Request
curl -X GET "https://gate.ummah.com/api/v1/billing_templates/{id}/clients/{client_id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/clients/{client_id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/clients/{client_id}/',
    headers=headers
)

print(response.json())
Response
{
  "id": "btc-a1b2c3d4-e5f6-7890",
  "client": { "email": "subscriber@example.com" },
  "is_subscription": true,
  "status": "active",
  "next_billing_on": 1703000000,
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Update Subscriber

PATCH/api/v1/billing_templates/{id}/clients/{client_id}/

Partially update a subscriber — for example, pause or resume their subscription (API route name: clients).

ParameterDescription
statusOPTIONALstringSubscription status: active or subscription_paused.
Request
curl -X PATCH "https://gate.ummah.com/api/v1/billing_templates/{id}/clients/{client_id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "status": "subscription_paused"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "status": "subscription_paused"
};

const { data: result } = await axios.patch(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/clients/{client_id}/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "status": "subscription_paused"
    }

response = requests.patch(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/clients/{client_id}/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "btc-a1b2c3d4-e5f6-7890",
  "client": { "email": "subscriber@example.com" },
  "is_subscription": true,
  "status": "active",
  "next_billing_on": 1703000000,
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}
Invoicing

One-Time Invoicing

Send invoices directly to clients or generate them from billing templates. Each client receives a unique checkout link.

Send an Invoice

POST/api/v1/billing/

Send a one-time invoice to one or several existing clients. Each client receives a unique checkout/invoice link.

ParameterDescription
brand_idREQUIREDuuidID of the brand issuing the invoice.
clientsREQUIREDarrayArray of BillingTemplateClient objects. Each item must include client_id.
purchase.productsREQUIREDarrayProducts to include in the invoice.
purchase.currencyOPTIONALstring3-letter ISO currency code.
Request
curl -X POST "https://gate.ummah.com/api/v1/billing/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "brand_id": "409eb80e-...",
    "clients": [
        {
            "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
        }
    ],
    "purchase": {
        "products": [
            {
                "name": "Consulting",
                "price": 50000
            }
        ],
        "currency": "USD"
    }
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "brand_id": "409eb80e-...",
  "clients": [
    {
      "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
    }
  ],
  "purchase": {
    "products": [
      {
        "name": "Consulting",
        "price": 50000
      }
    ],
    "currency": "USD"
  }
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/billing/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "brand_id": "409eb80e-...",
      "clients": [
        {
          "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
        }
      ],
      "purchase": {
        "products": [
          {
            "name": "Consulting",
            "price": 50000
          }
        ],
        "currency": "USD"
      }
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/billing/',
    headers=headers,
    json=data
)

print(response.json())
Response
[{
  "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "status": "created",
  "checkout_url": "https://pay.ummah.com/checkout/d6c9...",
  "invoice_url": "https://pay.ummah.com/invoice/d6c9...",
  "client": { "email": "customer@example.com", "full_name": "John Doe" },
  "purchase": { "total_amount": 2999, "currency": "USD" },
  "created_on": 1700000000
}]
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Send Invoice from Template

POST/api/v1/billing_templates/{id}/send_invoice/

Generate an invoice purchase from a billing template for an existing client. The response is a Purchase (with checkout_url / invoice_url).

ParameterDescription
client_idREQUIREDuuidID of an existing Client object.
Request
curl -X POST "https://gate.ummah.com/api/v1/billing_templates/{id}/send_invoice/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/billing_templates/{id}/send_invoice/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "client_id": "b79d3df6-2f69-4426-acee-eda049d83e18"
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/billing_templates/{id}/send_invoice/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "d6c9e9e0-1b5a-4c6e-8b3a-0f1a2b3c4d5e",
  "status": "created",
  "checkout_url": "https://pay.ummah.com/checkout/d6c9...",
  "invoice_url": "https://pay.ummah.com/invoice/d6c9...",
  "client": { "email": "customer@example.com", "full_name": "John Doe" },
  "purchase": { "total_amount": 2999, "currency": "USD" },
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}
Customers & Cards

Customer Management

Create and manage customer records with saved payment methods. Use recurring tokens for one-click payments and subscription renewals.

Create a Client

POST/api/v1/clients/

Create a new client record to store customer contact information.

ParameterDescription
emailREQUIREDstring (email)Client email address.
full_nameOPTIONALstringClient full name.
phoneOPTIONALstringClient phone number in E.164 format.
street_addressOPTIONALstringBilling street address.
countryOPTIONALstring2-letter ISO country code.
cityOPTIONALstringCity.
zip_codeOPTIONALstringZIP / postal code.
Request
curl -X POST "https://gate.ummah.com/api/v1/clients/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "email": "client@example.com",
    "full_name": "John Doe",
    "phone": "+1234567890",
    "country": "US"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "email": "client@example.com",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "country": "US"
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/clients/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "email": "client@example.com",
      "full_name": "John Doe",
      "phone": "+1234567890",
      "country": "US"
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/clients/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
  "email": "client@example.com",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "country": "US",
  "type": "personal",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

List Clients

GET/api/v1/clients/

Retrieve a paginated list of all client records for your account.

Request
curl -X GET "https://gate.ummah.com/api/v1/clients/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/clients/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/clients/',
    headers=headers
)

print(response.json())
Response
{
  "results": [{
    "id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
    "email": "client@example.com",
    "full_name": "John Doe",
    "phone": "+1234567890",
    "country": "US",
    "type": "personal",
    "created_on": 1700000000
  }],
  "next": null,
  "previous": null
}

Retrieve a Client

GET/api/v1/clients/{id}/

Retrieve a specific client by their ID.

Request
curl -X GET "https://gate.ummah.com/api/v1/clients/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/clients/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/clients/{id}/',
    headers=headers
)

print(response.json())
Response
{
  "id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
  "email": "client@example.com",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "country": "US",
  "type": "personal",
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Update a Client

PUT/api/v1/clients/{id}/

Replace all fields of a client record.

ParameterDescription
emailREQUIREDstringClient email address.
Request
curl -X PUT "https://gate.ummah.com/api/v1/clients/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "email": "updated@example.com",
    "full_name": "Jane Doe",
    "country": "GB"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "email": "updated@example.com",
  "full_name": "Jane Doe",
  "country": "GB"
};

const { data: result } = await axios.put(
  'https://gate.ummah.com/api/v1/clients/{id}/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "email": "updated@example.com",
      "full_name": "Jane Doe",
      "country": "GB"
    }

response = requests.put(
    'https://gate.ummah.com/api/v1/clients/{id}/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
  "email": "client@example.com",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "country": "US",
  "type": "personal",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Partially Update a Client

PATCH/api/v1/clients/{id}/

Update specific fields of a client record without replacing the entire object.

Request
curl -X PATCH "https://gate.ummah.com/api/v1/clients/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "full_name": "Updated Name"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "full_name": "Updated Name"
};

const { data: result } = await axios.patch(
  'https://gate.ummah.com/api/v1/clients/{id}/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "full_name": "Updated Name"
    }

response = requests.patch(
    'https://gate.ummah.com/api/v1/clients/{id}/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
  "email": "client@example.com",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "country": "US",
  "type": "personal",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Delete a Client

DELETE/api/v1/clients/{id}/

Permanently delete a client record and their associated data.

Request
curl -X DELETE "https://gate.ummah.com/api/v1/clients/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.delete(
  'https://gate.ummah.com/api/v1/clients/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.delete(
    'https://gate.ummah.com/api/v1/clients/{id}/',
    headers=headers
)

print(response.status_code)
Response
(empty — 204 No Content)
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Get Payment Method

GET/api/v1/clients/{id}/recurring_tokens/{id}/

Retrieve a specific recurring payment token by its ID.

Request
curl -X GET "https://gate.ummah.com/api/v1/clients/{id}/recurring_tokens/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/clients/{id}/recurring_tokens/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/clients/{id}/recurring_tokens/{id}/',
    headers=headers
)

print(response.json())
Response
{
  "id": "tok-a1b2c3d4-e5f6-7890",
  "payment_method": "card",
  "description": "Visa **** 4242",
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Delete Payment Method

DELETE/api/v1/clients/{id}/recurring_tokens/{id}/

Delete a saved recurring token, preventing it from being used for future charges.

Request
curl -X DELETE "https://gate.ummah.com/api/v1/clients/{id}/recurring_tokens/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.delete(
  'https://gate.ummah.com/api/v1/clients/{id}/recurring_tokens/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.delete(
    'https://gate.ummah.com/api/v1/clients/{id}/recurring_tokens/{id}/',
    headers=headers
)

print(response.status_code)
Response
(empty — 204 No Content)
{"error": "Not Found", "detail": "Object with this ID does not exist."}
Reporting & Payouts

Reporting & Financials

Access real-time balance data, transaction turnover reports, and generate downloadable financial statements in CSV or XLSX format.

Get Balance Data

GET/api/v1/balance/

Retrieve detailed balance data for your account.

Request
curl -X GET "https://gate.ummah.com/api/v1/balance/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/balance/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/balance/',
    headers=headers
)

print(response.json())
Response
{
  "balance": [
    { "currency": "USD", "amount": 125000 },
    { "currency": "EUR", "amount": 87500 }
  ]
}

Company Balance

GET/api/v1/account/json/balance/

Get the current balance of your company account, broken down by currency.

Request
curl -X GET "https://gate.ummah.com/api/v1/account/json/balance/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/account/json/balance/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/account/json/balance/',
    headers=headers
)

print(response.json())
Response
{
  "balance": [
    { "currency": "USD", "amount": 125000 },
    { "currency": "EUR", "amount": 87500 }
  ]
}

Company Turnover

GET/api/v1/account/json/turnover/

Get transaction turnover data for your company, grouped by currency and time period.

Request
curl -X GET "https://gate.ummah.com/api/v1/account/json/turnover/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/account/json/turnover/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/account/json/turnover/',
    headers=headers
)

print(response.json())
Response
{
  "turnover": [
    { "currency": "USD", "paid_amount": 1250000, "period": "2024-01" },
    { "currency": "USD", "paid_amount": 980000,  "period": "2024-02" }
  ]
}

Schedule Statement Generation

POST/api/v1/company_statements/

Schedule a financial statement for generation. Once processing completes, a download_url becomes available.

ParameterDescription
formatREQUIREDstringOutput format: csv or xlsx.
timezoneOPTIONALstringTimezone for timestamps, e.g. UTC, Asia/Dubai.
query_stringOPTIONALstringQuery parameters to filter statement data.
Request
curl -X POST "https://gate.ummah.com/api/v1/company_statements/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "format": "csv",
    "timezone": "UTC"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "format": "csv",
  "timezone": "UTC"
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/company_statements/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "format": "csv",
      "timezone": "UTC"
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/company_statements/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "stmt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "company",
  "format": "csv",
  "status": "processing",
  "download_url": null,
  "timezone": "UTC",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

List Statements

GET/api/v1/company_statements/

List all generated and pending statements for your account.

Request
curl -X GET "https://gate.ummah.com/api/v1/company_statements/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/company_statements/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/company_statements/',
    headers=headers
)

print(response.json())
Response
{
  "results": [{
    "id": "stmt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "type": "company",
    "format": "csv",
    "status": "processing",
    "download_url": null,
    "timezone": "UTC",
    "created_on": 1700000000
  }],
  "next": null,
  "previous": null
}

Retrieve a Statement

GET/api/v1/company_statements/{id}/

Retrieve a statement by its ID. Check status and download_url.

Request
curl -X GET "https://gate.ummah.com/api/v1/company_statements/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/company_statements/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/company_statements/{id}/',
    headers=headers
)

print(response.json())
Response
{
  "id": "stmt-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "company",
  "format": "csv",
  "status": "processing",
  "download_url": "https://storage.ummah.com/stmt/...",
  "timezone": "UTC",
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Cancel Statement

POST/api/v1/company_statements/{id}/cancel/

Cancel a pending statement generation.

Request
curl -X POST "https://gate.ummah.com/api/v1/company_statements/{id}/cancel/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/company_statements/{id}/cancel/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.post(
    'https://gate.ummah.com/api/v1/company_statements/{id}/cancel/',
    headers=headers
)

print(response.json())
Response
{"status": "cancelled"}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Callbacks & Webhooks

When a payment event occurs, the gateway sends a signed POST request to your configured callback URL. Verify the X-Signature header using the gateway’s RSA-SHA256 signature and the correct public key.

Public key source: for Purchase.success_callback, fetch the company callback key via GET /public_key/. For Webhook deliveries, use the public_key field on the Webhook object itself.

Events

Common events: purchase.paidpurchase.payment_failurepurchase.refundedpurchase.cancelledpurchase.subscription_charge_failurebilling_template.client_activated

Best Practices

Return a 2xx response quickly to acknowledge receipt. Process the event asynchronously to avoid gateway timeout. The gateway retries failed deliveries.

Webhook Verification Guide

Every callback delivery includes an X-Signature header containing a base64-encoded RSA PKCS#1 v1.5 signature over the SHA-256 digest of the raw request body. Always verify this signature before processing any event, and add replay protection + idempotency so retries don’t double-charge or double-fulfill.

Step 1 — Retrieve the public key
For Purchase.success_callback deliveries, fetch the company callback public key via GET /public_key/. For Webhook deliveries, use the public_key value on the Webhook you created/retrieved. Store it as PEM text (env var is fine) and cache it. Refresh if verification starts failing (key rotation).
Step 2 — Read the raw request body
You must use the raw (un-parsed) bytes for signature verification. Parsing the body first (e.g. with express.json()) will change whitespace and break the signature check.
Step 3 — Verify the RSA signature
Base64-decode X-Signature to get the signature bytes. Verify it against the raw body using the public key with RSA PKCS#1 v1.5 + SHA-256.
Step 4 — Prevent replay + handle retries safely
Reject old events (timestamp tolerance) and store each event.id (or object.id) for 24h to ignore duplicates. Your handler should be idempotent.
Step 5 — Respond with 2xx quickly
Return HTTP 200 immediately after verification. If your handler takes too long, the gateway will retry the delivery. Process heavy logic asynchronously.
Webhook Handler
const express = require('express');
const crypto  = require('crypto');

const app    = express();
const PUBLIC_KEY_PEM = process.env.UMMAH_WEBHOOK_PUBLIC_KEY; // PEM text (from Webhook.public_key or GET /public_key/)

// IMPORTANT: use raw body parser — do NOT use express.json() here
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sigHeader  = req.headers['x-signature'] || '';
  const rawBody    = req.body; // Buffer
  const sigB64 = String(sigHeader || '').trim();
  const signature = Buffer.from(sigB64, 'base64');

  const verifier = crypto.createVerify('RSA-SHA256');
  verifier.update(rawBody);
  verifier.end();

  const ok = verifier.verify(PUBLIC_KEY_PEM, signature);
  if (!ok) return res.status(403).json({ error: 'Invalid signature' });

  const event = JSON.parse(rawBody.toString('utf8'));
  console.log('Event received:', event.type);
  // Process idempotently. Deliveries may be retried.
  res.sendStatus(200);
});

app.listen(3000);
import os
from flask import Flask, request, abort
from base64 import b64decode

# pip install cryptography
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature

app    = Flask(__name__)
PUBLIC_KEY_PEM = os.environ['UMMAH_WEBHOOK_PUBLIC_KEY'].encode()  # PEM text (from Webhook.public_key or GET /public_key/)
PUBLIC_KEY = serialization.load_pem_public_key(PUBLIC_KEY_PEM)

@app.route('/webhook', methods=['POST'])
def webhook():
    sig_header = request.headers.get('X-Signature', '')
    raw_body   = request.get_data()  # raw bytes — do NOT use request.json here
    signature = b64decode((sig_header or '').strip() or b'')

    try:
        PUBLIC_KEY.verify(
            signature,
            raw_body,
            padding.PKCS1v15(),
            hashes.SHA256(),
        )
    except (InvalidSignature, ValueError):
        abort(403)

    event = request.get_json(force=True)
    print('Event received:', event.get('type'))
    return '', 200

if __name__ == '__main__':
    app.run(port=3000)
<?php

$publicKeyPem  = getenv('UMMAH_WEBHOOK_PUBLIC_KEY'); // PEM text (from Webhook.public_key or GET /public_key/)
$payload = file_get_contents('php://input'); // raw body
$sigHeader = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

$sig = base64_decode(trim($sigHeader), true);
if ($sig === false) {
    http_response_code(403);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// openssl_verify returns 1 for valid, 0 for invalid, -1 for error
$ok = openssl_verify($payload, $sig, $publicKeyPem, OPENSSL_ALGO_SHA256);
if ($ok !== 1) {
    http_response_code(403);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$event = json_decode($payload, true);
error_log('Event received: ' . $event['type']);

http_response_code(200);
Troubleshooting signature mismatches
  • Verify you used the raw body bytes (not parsed JSON) for verification.
  • Ensure you’re base64-decoding X-Signature before verifying.
  • Don’t re-serialize JSON (whitespace/newlines change the digest).
  • Ensure you’re using the correct public key: GET /public_key/ for success callbacks, or Webhook.public_key for webhook deliveries.

Security Best Practices

Follow these guidelines to keep your integration secure:

Verify every callback
Always validate the X-Signature header using the correct public key (GET /public_key/ for success callbacks, Webhook.public_key for webhooks). Never trust unverified payloads.
Use HTTPS everywhere
All callback URLs must use HTTPS. The gateway rejects plain HTTP endpoints.
Protect your secret key
Store sk_live_* keys in environment variables. Never commit them to source control or expose in client-side code.
Implement idempotency
Webhook deliveries may be retried. Use the purchase id to deduplicate events and prevent double-processing.
Prevent replay attacks
Reject old events (e.g. if created_on is outside a 5–10 minute window) and store each webhook event.id (or purchase id) for 24 hours so you can ignore duplicates.

Replay + idempotency (copy‑paste example)

Do three checks on every webhook delivery: (1) signature, (2) timestamp tolerance, (3) event-id deduplication.

Webhook handler
import crypto from 'crypto';
import express from 'express';

const app = express();
app.use(express.raw({ type: '*/*' })); // keep raw body for signature

const PUBLIC_KEY_PEM = process.env.UMMAH_WEBHOOK_PUBLIC_KEY; // PEM (Webhook.public_key or GET /public_key/)
const MAX_SKEW_SECONDS = 10 * 60;

// Pseudo storage helpers (use Redis/DB in production)
async function hasSeenEvent(eventId) { return false; }
async function rememberEvent(eventId) { /* TTL 24h */ }

app.post('/webhook', async (req, res) => {
  const raw = req.body; // Buffer
  const sigHeader = req.header('X-Signature') || '';
  const signature = Buffer.from(String(sigHeader || '').trim(), 'base64');

  const verifier = crypto.createVerify('RSA-SHA256');
  verifier.update(raw);
  verifier.end();
  const ok = verifier.verify(PUBLIC_KEY_PEM, signature);
  if(!ok) return res.status(403).send('invalid signature');

  const event = JSON.parse(raw.toString('utf8'));
  const createdOn = Number(event.created_on || event.timestamp || 0);
  const now = Math.floor(Date.now() / 1000);

  if (!createdOn || Math.abs(now - createdOn) > MAX_SKEW_SECONDS) {
    return res.status(400).send('stale event');
  }

  const eventId = event.id || event.event_id || event.object?.id;
  if (eventId && await hasSeenEvent(eventId)) return res.status(200).send('duplicate');
  if (eventId) await rememberEvent(eventId);

  // Process (make your handler idempotent too)
  // e.g. if event.type === 'purchase.paid' then fulfill order
  return res.status(200).send('ok');
});
import json
import time
import os
from base64 import b64decode

# pip install cryptography
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature

PUBLIC_KEY_PEM = os.environ["UMMAH_WEBHOOK_PUBLIC_KEY"].encode()  # PEM (Webhook.public_key or GET /public_key/)
PUBLIC_KEY = serialization.load_pem_public_key(PUBLIC_KEY_PEM)
MAX_SKEW_SECONDS = 10 * 60

# Pseudo storage helpers (use Redis/DB in production)
def has_seen_event(event_id: str) -> bool:
    return False

def remember_event(event_id: str) -> None:
    pass  # TTL 24h

def handle_webhook(raw_body: bytes, x_signature: str):
    signature = b64decode((x_signature or "").strip() or b"")
    try:
        PUBLIC_KEY.verify(
            signature,
            raw_body,
            padding.PKCS1v15(),
            hashes.SHA256(),
        )
    except (InvalidSignature, ValueError):
        return 403, "invalid signature"

    event = json.loads(raw_body.decode("utf-8"))
    created_on = int(event.get("created_on") or event.get("timestamp") or 0)
    now = int(time.time())

    if not created_on or abs(now - created_on) > MAX_SKEW_SECONDS:
        return 400, "stale event"

    event_id = event.get("id") or event.get("event_id") or (event.get("object") or {}).get("id")
    if event_id and has_seen_event(event_id):
        return 200, "duplicate"
    if event_id:
        remember_event(event_id)

    # Process idempotently
    return 200, "ok"
<?php
// Pseudo storage helpers (use Redis/DB in production)
function has_seen_event($event_id) { return false; }
function remember_event($event_id) { /* TTL 24h */ }

$publicKeyPem = getenv('UMMAH_WEBHOOK_PUBLIC_KEY'); // PEM (Webhook.public_key or GET /public_key/)
$max_skew_seconds = 10 * 60;

$raw = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$sig = base64_decode(trim($sigHeader), true);
if ($sig === false) {
  http_response_code(403);
  echo 'invalid signature';
  exit;
}

$ok = openssl_verify($raw, $sig, $publicKeyPem, OPENSSL_ALGO_SHA256);
if ($ok !== 1) {
  http_response_code(403);
  echo 'invalid signature';
  exit;
}

$event = json_decode($raw, true);
$created_on = intval($event['created_on'] ?? $event['timestamp'] ?? 0);
$now = time();

if (!$created_on || abs($now - $created_on) > $max_skew_seconds) {
  http_response_code(400);
  echo 'stale event';
  exit;
}

$event_id = $event['id'] ?? $event['event_id'] ?? ($event['object']['id'] ?? null);
if ($event_id && has_seen_event($event_id)) { http_response_code(200); echo 'duplicate'; exit; }
if ($event_id) remember_event($event_id);

// Process idempotently
http_response_code(200);
echo 'ok';

Webhook Event Simulator

Preview the exact JSON payload your server receives for each event. Copy the payload or replay it via cURL to test your webhook handler locally.

Event type
Payload
Local testing — Run ngrok http 3000 to expose your local server, then set your webhook URL in the dashboard to the ngrok tunnel. Select an event above, copy the cURL replay command, and fire it against your handler to test end-to-end without a real payment.
Webhooks & Security

Webhook Endpoints

Register webhook endpoints to receive real-time notifications. The gateway signs each delivery with RSA-SHA256 via the X-Signature header so you can verify authenticity.

Create a Webhook

POST/api/v1/webhooks/

Register a new webhook endpoint to receive event notifications.

ParameterDescription
callbackREQUIREDstring (url)HTTPS URL to POST event payloads to.
titleOPTIONALstringFriendly name for this webhook.
eventsOPTIONALarrayList of event types to subscribe to. e.g. ["purchase.paid"].
all_eventsOPTIONALbooleanSet to true to receive all event types.
Request
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const webhook = await ummah.webhooks.create({
  callback: 'https://mysite.com/webhook',
  title: 'Order Notifications',
  events: ['purchase.paid', 'purchase.payment_failure'],
});

console.log(webhook.id);
curl -X POST "https://gate.ummah.com/api/v1/webhooks/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "callback": "https://mysite.com/webhook",
    "title": "Order Notifications",
    "events": [
        "purchase.paid",
        "purchase.payment_failure"
    ]
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "callback": "https://mysite.com/webhook",
  "title": "Order Notifications",
  "events": [
    "purchase.paid",
    "purchase.payment_failure"
  ]
};

const { data: result } = await axios.post(
  'https://gate.ummah.com/api/v1/webhooks/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "callback": "https://mysite.com/webhook",
      "title": "Order Notifications",
      "events": [
        "purchase.paid",
        "purchase.payment_failure"
      ]
    }

response = requests.post(
    'https://gate.ummah.com/api/v1/webhooks/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "wh-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "title": "My Webhook",
  "callback": "https://mysite.com/webhook",
  "events": ["purchase.paid", "purchase.payment_failure"],
  "all_events": false,
  "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----",
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

List Webhooks

GET/api/v1/webhooks/

Retrieve all registered webhook endpoints for your account.

Request
curl -X GET "https://gate.ummah.com/api/v1/webhooks/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/webhooks/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/webhooks/',
    headers=headers
)

print(response.json())
Response
{
  "results": [{
    "id": "wh-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "title": "My Webhook",
    "callback": "https://mysite.com/webhook",
    "events": ["purchase.paid", "purchase.payment_failure"],
    "all_events": false,
    "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----",
    "created_on": 1700000000
  }],
  "next": null,
  "previous": null
}

Retrieve a Webhook

GET/api/v1/webhooks/{id}/

Retrieve a specific webhook by its ID.

Request
curl -X GET "https://gate.ummah.com/api/v1/webhooks/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/webhooks/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/webhooks/{id}/',
    headers=headers
)

print(response.json())
Response
{
  "id": "wh-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "title": "My Webhook",
  "callback": "https://mysite.com/webhook",
  "events": ["purchase.paid", "purchase.payment_failure"],
  "all_events": false,
  "created_on": 1700000000
}
{"error": "Not Found", "detail": "Object with this ID does not exist."}

Update a Webhook

PUT/api/v1/webhooks/{id}/

Replace all fields of a webhook endpoint.

Request
curl -X PUT "https://gate.ummah.com/api/v1/webhooks/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "callback": "https://mysite.com/new-webhook",
    "title": "Updated Webhook",
    "all_events": true
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "callback": "https://mysite.com/new-webhook",
  "title": "Updated Webhook",
  "all_events": true
};

const { data: result } = await axios.put(
  'https://gate.ummah.com/api/v1/webhooks/{id}/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "callback": "https://mysite.com/new-webhook",
      "title": "Updated Webhook",
      "all_events": True
    }

response = requests.put(
    'https://gate.ummah.com/api/v1/webhooks/{id}/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "wh-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "title": "My Webhook",
  "callback": "https://mysite.com/webhook",
  "events": ["purchase.paid", "purchase.payment_failure"],
  "all_events": false,
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Partially Update a Webhook

PATCH/api/v1/webhooks/{id}/

Update specific fields of a webhook endpoint.

Request
curl -X PATCH "https://gate.ummah.com/api/v1/webhooks/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
  -H "Content-Type: application/json" \
  -d '{
    "title": "Renamed Webhook"
}'
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const data = {
  "title": "Renamed Webhook"
};

const { data: result } = await axios.patch(
  'https://gate.ummah.com/api/v1/webhooks/{id}/',
  data,
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
    'Content-Type': 'application/json',
}

data = {
      "title": "Renamed Webhook"
    }

response = requests.patch(
    'https://gate.ummah.com/api/v1/webhooks/{id}/',
    headers=headers,
    json=data
)

print(response.json())
Response
{
  "id": "wh-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "title": "My Webhook",
  "callback": "https://mysite.com/webhook",
  "events": ["purchase.paid", "purchase.payment_failure"],
  "all_events": false,
  "created_on": 1700000000
}
{"error": "Bad Request", "details": {"field": ["This field is required."]}}

Delete a Webhook

DELETE/api/v1/webhooks/{id}/

Delete a webhook endpoint. No further deliveries will be sent.

Request
curl -X DELETE "https://gate.ummah.com/api/v1/webhooks/{id}/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.delete(
  'https://gate.ummah.com/api/v1/webhooks/{id}/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.delete(
    'https://gate.ummah.com/api/v1/webhooks/{id}/',
    headers=headers
)

print(response.status_code)
Response
(empty — 204 No Content)
{"error": "Not Found", "detail": "Object with this ID does not exist."}

List Webhook Deliveries

GET/api/v1/webhooks/deliveries/

List recent webhook delivery attempts for a specific source object. Requires id (UUID of the source object) and source_type (purchase, payment, payout, billing_template_client).

Request
curl -X GET "https://gate.ummah.com/api/v1/webhooks/deliveries/?id={id}&source_type=purchase" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/webhooks/deliveries/?id={id}&source_type=purchase',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/webhooks/deliveries/?id={id}&source_type=purchase',
    headers=headers
)

print(response.json())
Response
{
  "results": [{
    "id": "wh-delivery-a1b2",
    "event": "purchase.paid",
    "status": "delivered",
    "response_code": 200,
    "sent_at": 1700001234,
    "payload": { "event": "purchase.paid", "object": { "id": "d6c9..." } }
  }],
  "next": null,
  "previous": null
}

Get Callback Public Key

GET/api/v1/public_key/

Retrieve the company callback public key used to verify Purchase.success_callback deliveries (RSA-SHA256). For Webhook deliveries, use the public_key field on the Webhook object.

Request
const axios = require('axios');

const { data } = await axios.get(
  'https://gate.ummah.com/api/v1/public_key/',
  { headers: { Authorization: `Bearer ${process.env.SECRET_KEY}` } }
);

console.log(data.public_key);
curl -X GET "https://gate.ummah.com/api/v1/public_key/" \
  -H "Authorization: Bearer $SECRET_KEY"
const axios = require('axios');
// import axios from 'axios'; // ES Modules

const config = {
  headers: {
    Authorization: `Bearer ${process.env.SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
};

const { data: result } = await axios.get(
  'https://gate.ummah.com/api/v1/public_key/',
  config
);

console.log(result);
import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ["SECRET_KEY"]}',
}

response = requests.get(
    'https://gate.ummah.com/api/v1/public_key/',
    headers=headers
)

print(response.json())
Response
{
  "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----"
}

SDKs

The docs are SDK-first: whenever possible, examples show the SDK call first and keep raw REST (cURL) as a fallback.

Install
npm install @ummah/payments
Set your key
# Windows PowerShell $env:UMMAH_SECRET_KEY="sk_test_your_key_here"
Pick your preferred language in the UI
Your language choice is remembered. When you click a tab (SDK/Node/Python/PHP/Go), we apply it across the page.
SDK usage
import Ummah from '@ummah/payments';

const ummah = new Ummah(process.env.UMMAH_SECRET_KEY);

const purchase = await ummah.purchases.create({
  brand_id: '409eb80e-3782-4b1d-afa8-b779759266a5',
  client: { email: 'customer@example.com' },
  purchase: { products: [{ name: 'T-Shirt', price: 2999, quantity: 1 }], currency: 'USD' },
  success_redirect: 'https://myshop.com/success',
  failure_redirect: 'https://myshop.com/failed',
});

console.log(purchase.checkout_url);
const axios = require('axios');

const { data } = await axios.post(
  'https://gate.ummah.com/api/v1/purchases/',
  {/* ... */},
  { headers: { Authorization: `Bearer ${process.env.SECRET_KEY}` } }
);

console.log(data.checkout_url);
import os, requests

r = requests.post(
  'https://gate.ummah.com/api/v1/purchases/',
  headers={'Authorization': f'Bearer {os.environ[\"SECRET_KEY\"]}'},
  json={},
  timeout=30,
)
print(r.json())
Note — If/when the official SDK interface differs, the code blocks in this doc can be updated without changing the underlying REST endpoints.
Reference

Common Patterns

Recipes for common integration scenarios.

One-Click Checkout

Save a card on the customer's first purchase using force_recurring: true. On subsequent orders, charge them directly server-side — no redirect, no re-entry of card details.

Step 1 — First purchase: save card
Create the initial purchase with force_recurring: true. After the customer completes checkout, the purchase object will contain a recurring_token.
Step 2 — Store the recurring token
Save purchase.recurring_token and purchase.id in your database against the customer record.
Step 3 — Charge future orders
Create a new purchase for the second order using the saved recurring_token. No redirect needed.
One-Click Checkout
const axios = require('axios');

const client = axios.create({
  baseURL: 'https://gate.ummah.com/api/v1',
  headers: { Authorization: `Bearer ${process.env.SECRET_KEY}` }
});

// --- First purchase: save card ---
const first = await client.post('/purchases/', {
  amount: '59.99',
  currency: 'USD',
  name: 'Order #1001',
  force_recurring: true,          // request card save
  success_redirect: 'https://yourapp.com/success',
  failure_redirect: 'https://yourapp.com/failure',
});
// Redirect customer to first.data.checkout_url
// After payment, first.data.recurring_token is available

// --- Future purchase: one-click checkout ---
const newPurchase = await client.post('/purchases/', {
  amount: '79.99',
  currency: 'USD',
  name: 'Order #1002',
  recurring_token: savedRecurringToken, // from first purchase
});
import requests
import os

BASE = 'https://gate.ummah.com/api/v1'
headers = {'Authorization': f'Bearer {os.environ["SECRET_KEY"]}'}

# --- First purchase: save card ---
first = requests.post(f'{BASE}/purchases/', headers=headers, json={
    'amount': '59.99',
    'currency': 'USD',
    'name': 'Order #1001',
    'force_recurring': True,
    'success_redirect': 'https://yourapp.com/success',
    'failure_redirect': 'https://yourapp.com/failure',
}).json()
# Redirect customer to first['checkout_url']
# After payment, first['recurring_token'] is available

# --- Future purchase: one-click checkout ---
new_purchase = requests.post(f'{BASE}/purchases/', headers=headers, json={
    'amount': '79.99',
    'currency': 'USD',
    'name': 'Order #1002',
    'recurring_token': saved_recurring_token,
}).json()

Pre-Authorization & Capture

Hold funds at booking time with skip_capture: true and only settle when you ship or provide the service. Void the hold anytime before capture if the order is cancelled.

Step 1 — Authorize (hold) funds
Create the purchase with skip_capture: true. The customer is redirected to checkout. Funds are held but not settled — no money moves yet.
Step 2 — Capture when ready to fulfil
Call POST /purchases/{id}/capture/ with the final amount (can be less than or equal to the held amount). Funds are now settled.
Step 3 — Void if cancelled
If the order is cancelled before capture, call POST /purchases/{id}/release/ to void the authorization. Funds are released back to the customer immediately.
Pre-Authorization & Capture
const axios = require('axios');

const client = axios.create({
  baseURL: 'https://gate.ummah.com/api/v1',
  headers: { Authorization: `Bearer ${process.env.SECRET_KEY}` }
});

// Step 1: Authorize (hold) funds
const purchase = await client.post('/purchases/', {
  amount: '150.00',
  currency: 'USD',
  name: 'Hotel Booking #882',
  skip_capture: true,             // hold — do not settle yet
  success_redirect: 'https://yourapp.com/success',
  failure_redirect: 'https://yourapp.com/failure',
});
// Redirect customer: purchase.data.checkout_url

// Step 2: Capture when ready (e.g. at check-out)
await client.post(`/purchases/${purchase.data.id}/capture/`, {
  amount: '135.00'  // may differ from held amount
});

// --- OR ---

// Step 3: Void if cancelled
await client.post(`/purchases/${purchase.data.id}/release/`);
import requests
import os

BASE = 'https://gate.ummah.com/api/v1'
headers = {'Authorization': f'Bearer {os.environ["SECRET_KEY"]}'}

# Step 1: Authorize (hold) funds
purchase = requests.post(f'{BASE}/purchases/', headers=headers, json={
    'amount': '150.00',
    'currency': 'USD',
    'name': 'Hotel Booking #882',
    'skip_capture': True,
    'success_redirect': 'https://yourapp.com/success',
    'failure_redirect': 'https://yourapp.com/failure',
}).json()
# Redirect customer: purchase['checkout_url']

# Step 2: Capture when ready
requests.post(
    f'{BASE}/purchases/{purchase["id"]}/capture/',
    headers=headers,
    json={'amount': '135.00'}
)

# --- OR ---

# Step 3: Void if cancelled
requests.post(f'{BASE}/purchases/{purchase["id"]}/release/', headers=headers)

Partial Refunds

Pass an amount to POST /purchases/{id}/refund/ to refund less than the full total. You can issue multiple partial refunds as long as the cumulative amount does not exceed the original captured amount.

Subscription with Trial Period

Create a billing template, subscribe the customer, then pause billing during the trial. Resume when the trial ends.

Step 1 — Create a billing template
Define the recurring plan with POST /billing_templates/ including amount, currency, and billing period.
Step 2 — Subscribe client
Call POST /billing_templates/{id}/add_subscriber/ to enrol the customer. Set status: "subscription_paused" if starting a free trial.
Step 3 — Activate after trial
When the trial period ends, call PATCH /billing_templates/{id}/clients/{client_id}/ with {"status": "active"} to start billing.

Error Codes

Most API errors return a consistent JSON body with a machine-readable error code, a human-readable message, and an optional details object for field-level validation errors.

Example error responses
{
  "error": "card_declined",
  "message": "The card was declined by the issuing bank.",
  "details": {
    "decline_code": "insufficient_funds",
    "param": "payment_method"
  }
}
{
  "error": "Bad Request",
  "details": {
    "client.email": ["This field is required."]
  }
}
{
  "error": "Not Found",
  "detail": "Object with this ID does not exist."
}

Error Code Reference

CodeHTTP StatusDescriptionRetryable?
insufficient_funds402The card or account has insufficient funds to complete the transaction.No — ask customer to use a different payment method
card_declined402The card was declined by the issuing bank. Check details.decline_code for more info.No — ask customer to contact their bank
invalid_card400Card number, expiry, or CVV is invalid or malformed.No — fix the card details and retry
expired_card402The card's expiry date has passed.No — ask customer for a new card
authentication_required4023D Secure authentication is required. Redirect the customer to the checkout URL.Yes — redirect to checkout_url
invalid_api_key401The API key is missing, malformed, or revoked.No — check your Authorization header
rate_limit_exceeded429Too many requests in a short window. Slow down and retry.Yes — retry with exponential backoff
server_error500Unexpected internal error. Our team is automatically notified.Yes — retry after a short delay

HTTP Status Summary

HTTP StatusMeaningCommon Cause
400Bad RequestMissing required field, invalid format, or validation error
401UnauthorizedMissing or invalid API key
403ForbiddenKey does not have permission for this resource
404Not FoundObject ID does not exist
409ConflictOperation not allowed in current state (e.g. refunding an unpaid purchase)
422Unprocessable EntityRequest is well-formed but semantically invalid (e.g. duplicate idempotency key with different body)
429Rate LimitedToo many requests. Back off and retry with exponential delay
500 / 503Server ErrorInternal or temporary error. Retry after a short delay

Retry Guidance

Never retry 4xx errors without changing the request
Errors like invalid_card, invalid_api_key, or 400 Bad Request indicate a problem with your request data. Retrying without fixing the issue will always fail.
Retry 429 and 5xx with exponential backoff
Wait 1s, then 2s, then 4s (up to 3 attempts). Add random jitter (±200ms) to avoid thundering herd. The Retry-After response header, when present, gives the minimum wait time.
Use idempotency keys for POST requests
Pass an Idempotency-Key: <uuid> header on all POST requests. If your request times out and you are unsure whether it succeeded, retry with the same key — the gateway will return the original response without creating a duplicate.

Full API Reference

Quick index of all 47 endpoints. Click any row to jump to the full documentation.

Payments

Subscriptions

Invoicing

Customers

Webhooks & Security

Reporting