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.
Go live in 5 minutes with our REST API and client libraries.
SDKsSDK-first snippets with a consistent API across endpoints.
Accept PaymentsOne-time charges, pre-auth & capture, tokenized recurring payments.
SubscriptionsFull recurring billing with templates, webhooks, and lifecycle management.
Webhooks & SecurityRSA signature verification, idempotency keys, and secure key management.
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.
npm install @ummah/paymentsimport 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'])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_abc123import 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'])# 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.jsimport 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);purchase.checkout_url — a PCI-compliant hosted page. They enter their card details there; your server never handles raw card data.purchase.paid event to your callback URL. Verify the X-Signature header and fulfill the order. See the Webhook Verification Guide.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.
| Feature | Ummah | Stripe |
|---|---|---|
| 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 |
Authentication
All API requests must include your secret key in the Authorization header using
Bearer scheme.
Authorization: Bearer YOUR_SECRET_KEYsk_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"
The core transaction object. Holds products, payment status, checkout URL, and client info.
A customer record with contact details and optional saved payment methods (recurring tokens).
A reusable invoice or subscription plan. Used to create invoices or manage recurring billing.
A client enrolled on a billing template. Tracks recurring status and next billing date.
Your callback endpoint configuration. Used to receive signed events like purchase.paid.
Start here (recommended mental model)
checkout_url → verify the webhook → retrieve the Purchase for final status.clients).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.
{
"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
}{
"id": "c1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
"email": "client@example.com",
"full_name": "John Doe",
"phone": "+1234567890",
"country": "US",
"type": "personal",
"created_on": 1700000000
}{
"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
}{
"id": "btc-a1b2c3d4-e5f6-7890",
"client": { "email": "subscriber@example.com" },
"is_subscription": true,
"status": "active",
"next_billing_on": 1703000000,
"created_on": 1700000000
}{
"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" }
}
}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 Number | Result | CVC | Expiry |
|---|---|---|---|
| 4242 4242 4242 4242 | Success | Any 3 digits | Any future date |
| 4000 0000 0000 0002 | Declined | Any 3 digits | Any future date |
| 4000 0000 0000 3220 | 3D Secure required | Any 3 digits | Any future date |
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.
Go live — Sandbox → Production checklist
This checklist helps you ship safely. Most production incidents come from keys, webhooks, retries, and idempotency.
1) Keys & environments
sk_test_* locally and in staging. Never embed keys in client-side code.sk_live_* only in your production secrets manager (env vars). Treat keys like passwords.2) Webhooks (required)
event.id (or object.id) for 24h to ignore duplicates.3) Retries & idempotency (API calls)
429 and 5xx with exponential backoff. For POST, reuse the same Idempotency-Key across retries.purchase.id, final status, and webhook event.id in your DB for support/debugging.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)
| Status | What it means | What you do |
|---|---|---|
created | Purchase created; customer has not completed checkout yet. | Redirect to checkout_url. Optionally allow cancel. |
hold | Funds are held. Nothing has been captured yet. | Capture with POST /capture/ or release with POST /release/. |
pending_capture | Capture has been requested and is processing. | Wait for the status to transition (webhook/poll GET /purchases/{id}/). |
pending_release | Release has been requested and is processing. | Wait for the status to transition (webhook/poll GET /purchases/{id}/). |
paid | Funds are captured/settled. | Fulfill the order. Refund with POST /refund/ if needed. |
cancelled | Purchase was cancelled before payment completed. | Stop the flow; create a new purchase if the customer retries. |
released | Held funds were released. | Stop the flow; create a new purchase if needed. |
refunded | Funds were returned to the customer (full/partial). | Update your records; handle refund webhooks if you rely on them. |
purchase.paid), but always treat GET /purchases/{id}/ as the source of truth for the final status.
Create a Purchase
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.
| Parameter | Description |
|---|---|
brand_idREQUIREDuuid | ID of the brand to create this Purchase for. |
client.emailREQUIREDstring | Customer email address. |
purchase.productsREQUIREDarray | Products array. Each item: {name, price, quantity}. |
purchase.currencyOPTIONALstring | 3-letter ISO currency code. Defaults to brand currency. |
success_redirectOPTIONALstring | URL to redirect to after successful payment. |
failure_redirectOPTIONALstring | URL to redirect to after payment failure. |
success_callbackOPTIONALstring | URL to POST a signed payment notification to. |
skip_captureOPTIONALboolean | Place funds on hold; capture later via POST /capture/. |
force_recurringOPTIONALboolean | Save payment method for future recurring charges. |
send_receiptOPTIONALboolean | Email a receipt to the client after payment. |
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()){
"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
Retrieve the full details of a purchase by its ID, including current status, payment data, and client info.
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()){
"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
Cancel a purchase that hasn’t been completed yet. Use this to guarantee it can’t be paid later.
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()){"status": "cancelled"}{"error": "Not Found", "detail": "Object with this ID does not exist."}Release Held Funds
Release funds reserved for a purchase on hold (status == hold). Used with skip_capture: true (API route name: release).
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()){"status": "released"}{"error": "Not Found", "detail": "Object with this ID does not exist."}Capture a Purchase
Capture funds reserved for a purchase on hold (status == hold). Optionally specify a partial amount in the smallest currency unit.
| Parameter | Description |
|---|---|
amountOPTIONALinteger | Amount to capture in smallest currency unit (e.g. cents). Defaults to full held amount. |
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()){
"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
Refund a paid purchase in full or partially. Optionally pass an internal reference for your records.
| Parameter | Description |
|---|---|
amountOPTIONALinteger | Amount to refund in smallest currency unit. Defaults to full amount. |
referenceOPTIONALstring | Internal refund reference for your records. |
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()){
"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
Manually mark a purchase as paid. Useful for cash/offline payments recorded in the system.
| Parameter | Description |
|---|---|
paid_onOPTIONALtimestamp | Unix timestamp of when payment was received. Defaults to now. |
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()){
"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
Re-sends the invoice email for this purchase to the client.
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()){"status": "sent"}{"error": "Not Found", "detail": "Object with this ID does not exist."}Detach Payment Method
Detach the saved payment method used for future charges (API route name: delete_recurring_token).
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()){"status": "deleted"}{"error": "Not Found", "detail": "Object with this ID does not exist."}List Payment Methods
Get the list of payment methods available for a brand/currency. Requires brand_id and currency query parameters.
curl -X GET "https://gate.ummah.com/api/v1/payment_methods/?brand_id={brand_id}¤cy=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()){
"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 the list of payout methods available for a brand/currency. Requires brand_id and currency query parameters.
curl -X GET "https://gate.ummah.com/api/v1/payout_methods/?brand_id={brand_id}¤cy=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()){
"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"]
}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.
checkout_url.checkout_url.X-Signature (see Webhook Verification Guide) and dedupe events.Accept One-Time Payments
Use hosted checkout to collect payment details and complete PCI-compliant payments without handling raw card data.
Idempotency-Key to avoid duplicates (see Error Codes).Pre‑Authorization & Capture
Authorize funds first, then capture later (hotels, marketplaces, delayed fulfillment).
skip_capture: true via POST /purchases/.Refunds
Refund fully or partially after a purchase is paid.
Subscriptions
Create a plan (billing template), subscribe a client, then pause/resume when needed.
is_subscription: true.Invoicing
Send one-time invoices to clients, or generate invoices from templates.
Customers & Saved Cards
Save a payment method on first checkout, then charge later without redirect (“one-click checkout”).
force_recurring: true via POST /purchases/.recurring_token to support one-click checkout.Reporting & Payouts
Track balances and generate statements for accounting.
Webhooks & Security
Webhooks give you reliable payment status updates. Always verify signatures and prevent replay.
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
status: "subscription_paused" until the trial ends. Resume with PATCH /billing_templates/{id}/clients/{client_id}/.
Create Billing Template
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.
| Parameter | Description |
|---|---|
purchaseREQUIREDobject | PurchaseDetails object: products, currency, etc. |
is_subscriptionREQUIREDboolean | true for subscription, false for one-time invoicing. |
brand_idREQUIREDuuid | ID of the brand for this template. |
subscription_periodOPTIONALinteger | How often to bill (required when is_subscription: true). Example: 1. |
subscription_period_unitsOPTIONALstring | Units for subscription_period (required when is_subscription: true): days, weeks, months, years. |
number_of_billing_cyclesOPTIONALinteger | Limit billing cycles per client. 0 = unlimited. |
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()){
"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
List all billing templates for your account.
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()){
"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
Retrieve a billing template by its ID.
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()){
"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
Replace all fields of a billing template.
| Parameter | Description |
|---|---|
purchaseREQUIREDobject | Updated PurchaseDetails object. |
is_subscriptionREQUIREDboolean | Subscription mode flag. |
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()){
"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 a billing template by its ID. This does not cancel active subscriptions.
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)(empty — 204 No Content){"error": "Not Found", "detail": "Object with this ID does not exist."}Subscribe
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).
| Parameter | Description |
|---|---|
client_idREQUIREDuuid | ID of an existing Client to subscribe. |
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()){
"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
List all subscribers for this billing template (Plan) (API route name: clients).
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()){
"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
Retrieve a subscriber for this billing template (Plan) (API route name: clients).
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()){
"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
Partially update a subscriber — for example, pause or resume their subscription (API route name: clients).
| Parameter | Description |
|---|---|
statusOPTIONALstring | Subscription status: active or subscription_paused. |
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()){
"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."]}}One-Time Invoicing
Send invoices directly to clients or generate them from billing templates. Each client receives a unique checkout link.
Send an Invoice
Send a one-time invoice to one or several existing clients. Each client receives a unique checkout/invoice link.
| Parameter | Description |
|---|---|
brand_idREQUIREDuuid | ID of the brand issuing the invoice. |
clientsREQUIREDarray | Array of BillingTemplateClient objects. Each item must include client_id. |
purchase.productsREQUIREDarray | Products to include in the invoice. |
purchase.currencyOPTIONALstring | 3-letter ISO currency code. |
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())[{
"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
Generate an invoice purchase from a billing template for an existing client. The response is a Purchase (with checkout_url / invoice_url).
| Parameter | Description |
|---|---|
client_idREQUIREDuuid | ID of an existing Client object. |
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()){
"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."]}}Customer Management
Create and manage customer records with saved payment methods. Use recurring tokens for one-click payments and subscription renewals.
Create a Client
Create a new client record to store customer contact information.
| Parameter | Description |
|---|---|
emailREQUIREDstring (email) | Client email address. |
full_nameOPTIONALstring | Client full name. |
phoneOPTIONALstring | Client phone number in E.164 format. |
street_addressOPTIONALstring | Billing street address. |
countryOPTIONALstring | 2-letter ISO country code. |
cityOPTIONALstring | City. |
zip_codeOPTIONALstring | ZIP / postal code. |
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()){
"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
Retrieve a paginated list of all client records for your account.
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()){
"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
Retrieve a specific client by their ID.
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()){
"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
Replace all fields of a client record.
| Parameter | Description |
|---|---|
emailREQUIREDstring | Client email address. |
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()){
"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
Update specific fields of a client record without replacing the entire object.
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()){
"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
Permanently delete a client record and their associated data.
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)(empty — 204 No Content){"error": "Not Found", "detail": "Object with this ID does not exist."}Get Payment Method
Retrieve a specific recurring payment token by its ID.
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()){
"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 a saved recurring token, preventing it from being used for future charges.
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)(empty — 204 No Content){"error": "Not Found", "detail": "Object with this ID does not exist."}Reporting & Financials
Access real-time balance data, transaction turnover reports, and generate downloadable financial statements in CSV or XLSX format.
Get Balance Data
Retrieve detailed balance data for your account.
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()){
"balance": [
{ "currency": "USD", "amount": 125000 },
{ "currency": "EUR", "amount": 87500 }
]
}Company Balance
Get the current balance of your company account, broken down by currency.
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()){
"balance": [
{ "currency": "USD", "amount": 125000 },
{ "currency": "EUR", "amount": 87500 }
]
}Company Turnover
Get transaction turnover data for your company, grouped by currency and time period.
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()){
"turnover": [
{ "currency": "USD", "paid_amount": 1250000, "period": "2024-01" },
{ "currency": "USD", "paid_amount": 980000, "period": "2024-02" }
]
}Schedule Statement Generation
Schedule a financial statement for generation. Once processing completes, a download_url becomes available.
| Parameter | Description |
|---|---|
formatREQUIREDstring | Output format: csv or xlsx. |
timezoneOPTIONALstring | Timezone for timestamps, e.g. UTC, Asia/Dubai. |
query_stringOPTIONALstring | Query parameters to filter statement data. |
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()){
"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
List all generated and pending statements for your account.
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()){
"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
Retrieve a statement by its ID. Check status and download_url.
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()){
"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
Cancel a pending statement generation.
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()){"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.paid • purchase.payment_failure •
purchase.refunded • purchase.cancelled •
purchase.subscription_charge_failure • billing_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.
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).express.json()) will change whitespace and break the signature check.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.event.id (or object.id) for 24h to ignore duplicates. Your handler should be idempotent.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);- Verify you used the raw body bytes (not parsed JSON) for verification.
- Ensure you’re base64-decoding
X-Signaturebefore 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_keyfor webhook deliveries.
Security Best Practices
Follow these guidelines to keep your integration secure:
X-Signature header using the correct public key (GET /public_key/ for success callbacks, Webhook.public_key for webhooks). Never trust unverified payloads.sk_live_* keys in environment variables. Never commit them to source control or expose in client-side code.id to deduplicate events and prevent double-processing.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.
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.
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.
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
Register a new webhook endpoint to receive event notifications.
| Parameter | Description |
|---|---|
callbackREQUIREDstring (url) | HTTPS URL to POST event payloads to. |
titleOPTIONALstring | Friendly name for this webhook. |
eventsOPTIONALarray | List of event types to subscribe to. e.g. ["purchase.paid"]. |
all_eventsOPTIONALboolean | Set to true to receive all event types. |
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()){
"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
Retrieve all registered webhook endpoints for your account.
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()){
"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
Retrieve a specific webhook by its ID.
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()){
"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
Replace all fields of a webhook endpoint.
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()){
"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
Update specific fields of a webhook endpoint.
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()){
"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 a webhook endpoint. No further deliveries will be sent.
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)(empty — 204 No Content){"error": "Not Found", "detail": "Object with this ID does not exist."}List Webhook 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).
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()){
"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
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.
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()){
"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.
npm install @ummah/payments# 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);
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())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.
force_recurring: true. After the customer completes checkout, the purchase object will contain a recurring_token.purchase.recurring_token and purchase.id in your database against the customer record.recurring_token. No redirect needed.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.
skip_capture: true. The customer is redirected to checkout. Funds are held but not settled — no money moves yet.POST /purchases/{id}/capture/ with the final amount (can be less than or equal to the held amount). Funds are now settled.POST /purchases/{id}/release/ to void the authorization. Funds are released back to the customer immediately.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.
POST /billing_templates/ including amount, currency, and billing period.POST /billing_templates/{id}/add_subscriber/ to enrol the customer. Set status: "subscription_paused" if starting a free trial.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.
{
"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
| Code | HTTP Status | Description | Retryable? |
|---|---|---|---|
insufficient_funds | 402 | The card or account has insufficient funds to complete the transaction. | No — ask customer to use a different payment method |
card_declined | 402 | The card was declined by the issuing bank. Check details.decline_code for more info. | No — ask customer to contact their bank |
invalid_card | 400 | Card number, expiry, or CVV is invalid or malformed. | No — fix the card details and retry |
expired_card | 402 | The card's expiry date has passed. | No — ask customer for a new card |
authentication_required | 402 | 3D Secure authentication is required. Redirect the customer to the checkout URL. | Yes — redirect to checkout_url |
invalid_api_key | 401 | The API key is missing, malformed, or revoked. | No — check your Authorization header |
rate_limit_exceeded | 429 | Too many requests in a short window. Slow down and retry. | Yes — retry with exponential backoff |
server_error | 500 | Unexpected internal error. Our team is automatically notified. | Yes — retry after a short delay |
HTTP Status Summary
| HTTP Status | Meaning | Common Cause |
|---|---|---|
400 | Bad Request | Missing required field, invalid format, or validation error |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Key does not have permission for this resource |
404 | Not Found | Object ID does not exist |
409 | Conflict | Operation not allowed in current state (e.g. refunding an unpaid purchase) |
422 | Unprocessable Entity | Request is well-formed but semantically invalid (e.g. duplicate idempotency key with different body) |
429 | Rate Limited | Too many requests. Back off and retry with exponential delay |
500 / 503 | Server Error | Internal or temporary error. Retry after a short delay |
Retry Guidance
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-After response header, when present, gives the minimum wait time.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.