ITA Master Pay Payments API
Accept payments on your own website or app with ITA Master Pay, the same way you would with Stripe Checkout: your server creates a payment, the customer pays on a hosted checkout page, and your server is told the result with a signed webhook.
- API base URL:
https://itamaster-pay.com/api/v1 - Documentation: https://itamaster-pay.com/docs/api (raw Markdown: https://itamaster-pay.com/docs/api.md)
- Format: JSON over HTTPS. Send
Content-Type: application/json. - Version:
v1(in the URL). Fields may be added to responses; never remove or rename what is documented here.
1. How it works
Your server ITA Master Pay Customer
| POST /payments ------------------>| |
|<-- { id, checkout_url, ... } -------| |
| redirect the customer to checkout_url ------------------------------->|
| |<--- pays on the hosted page -----|
|<-- webhook: payment.paid ----------| |
| |--- redirect to success_url ----->|
| mark the order as paid |
- Create a payment with an amount, a currency and a description. You get back a
checkout_url. - Send the customer to
checkout_url. They enter their name and email and pay by card, Apple Pay, Google Pay or any method enabled on the platform. - Wait for the
payment.paidwebhook (or fetch the payment) and only then deliver the goods or mark the order as paid. Never trust the redirect tosuccess_urlalone: a customer can type that address by hand.
Money and currencies
- Amounts are integers in the smallest unit of the currency (cents).
4999with currencyUSDis $49.99. Every supported currency has two decimal places. - You can invoice in any supported currency (list below). The customer is always charged in USD, converted at the live exchange rate at the moment they press Purchase now. That rate is stored with the payment (
charge.exchange_rate), so the charged amount never changes afterwards. - Limits: a payment must be worth between $5.00 and $4,850.00 once converted to USD.
GET /currenciesreturns the exact minimum and maximum for each currency today. - Your earnings are always in USD: the amount charged, less payment fees and any platform fee. They appear in your seller panel and in
seller_earningon a paid payment.
Supported currencies (invoice in any of these; customers are charged in USD):
| Code | Currency | Code | Currency |
|---|---|---|---|
USD |
US Dollar | AED |
United Arab Emirates Dirham |
ARS |
Argentine Peso | AUD |
Australian Dollar |
BDT |
Bangladeshi Taka | BRL |
Brazilian Real |
CAD |
Canadian Dollar | CHF |
Swiss Franc |
CNY |
Chinese Renminbi Yuan | COP |
Colombian Peso |
CZK |
Czech Koruna | DKK |
Danish Krone |
EGP |
Egyptian Pound | EUR |
Euro |
GBP |
British Pound | GEL |
Georgian Lari |
GHS |
Ghanaian Cedi | HKD |
Hong Kong Dollar |
HUF |
Hungarian Forint | IDR |
Indonesian Rupiah |
ILS |
Israeli New Shekel | INR |
Indian Rupee |
KES |
Kenyan Shilling | LKR |
Sri Lankan Rupee |
MAD |
Moroccan Dirham | MXN |
Mexican Peso |
MYR |
Malaysian Ringgit | NGN |
Nigerian Naira |
NOK |
Norwegian Krone | NZD |
New Zealand Dollar |
PEN |
Peruvian Sol | PHP |
Philippine Peso |
PKR |
Pakistani Rupee | PLN |
Polish Złoty |
QAR |
Qatari Riyal | RON |
Romanian Leu |
RSD |
Serbian Dinar | SAR |
Saudi Riyal |
SEK |
Swedish Krona | SGD |
Singapore Dollar |
THB |
Thai Baht | TRY |
Turkish Lira |
TWD |
New Taiwan Dollar | UAH |
Ukrainian Hryvnia |
ZAR |
South African Rand |
2. Authentication
Create a secret key in the seller panel under Developers → API keys. Send it with every request:
Authorization: Bearer gw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Keys start with
gw_live_. The full key is shown once, when you create it; only a hash is stored, so a lost key cannot be recovered, only replaced. - Keep keys on your server. Never put one in browser JavaScript, a mobile app, or a public repository. Read it from an environment variable such as
PAY_API_KEY. - Create one key per integration so a leak can be revoked without touching the others. Revoking a key stops it immediately.
- A key acts on your account only. You can never read or change another seller's data.
3. Errors
Every error has the same shape and an HTTP status that matches:
{
"error": {
"type": "invalid_request_error",
"code": "validation_failed",
"message": "The minimum invoice amount is $5.00.",
"param": "amount",
"errors": { "amount": ["The minimum invoice amount is $5.00."] }
}
}
| HTTP | type |
When |
|---|---|---|
| 400 | invalid_request_error |
A malformed request, for example an unknown starting_after. |
| 401 | authentication_error |
api_key_missing or api_key_invalid (wrong, or revoked). |
| 403 | permission_error |
account_suspended or email_not_verified. |
| 404 | invalid_request_error |
resource_not_found: no such payment or endpoint on your account. |
| 405 | invalid_request_error |
method_not_allowed. |
| 409 | invalid_request_error |
idempotency_key_reused, payment_already_paid, webhook_limit_reached. |
| 422 | invalid_request_error |
validation_failed. param names the first bad field; errors lists every field. |
| 429 | rate_limit_error |
rate_limited. Wait for the Retry-After seconds. |
| 500 / 503 | api_error |
server_error, or api_unavailable when the API is switched off. Safe to retry. |
Branch on error.code, show error.message to developers (not to customers).
4. Idempotency
Send an Idempotency-Key header (any unique string up to 120 characters, for example order-1042) when creating a payment. If the request is retried with the same key, you get the original payment back (200 with Idempotent-Replayed: true) instead of a duplicate. Reusing a key with a different amount or currency returns 409 idempotency_key_reused. Use it on every create call: networks fail.
5. Rate limits
120 requests per minute per API key. Over the limit you get 429 with a Retry-After header.
6. Payments
The payment object
{
"id": "pay_01k7x2m9q3v8h5n4r6t1w0yzab",
"object": "payment",
"status": "open",
"amount": 4999,
"currency": "EUR",
"description": "Order #1042",
"reference": "order-1042",
"customer": { "name": "Jane Buyer", "email": "jane@example.com" },
"metadata": { "cart_id": "abc123" },
"checkout_url": "https://itamaster-pay.com/checkout/c/…",
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"expires_at": "2026-10-12T10:00:00+00:00",
"created_at": "2026-10-11T10:00:00+00:00",
"paid_at": null,
"source": "api",
"livemode": true,
"charge": null,
"seller_earning": null
}
| Field | Meaning |
|---|---|
id |
The payment id, pay_…. Store it with your order. |
status |
open (waiting for the customer), paid, cancelled, or expired. |
amount, currency |
What you invoiced, in minor units and ISO 4217 code. |
description |
Shown to the customer on the payment page and receipt (max 120 characters). |
reference |
Your own id for this payment, such as an order number (max 255). Searchable with GET /payments?reference=…. |
metadata |
Up to 20 string key/value pairs you want stored and echoed back in webhooks. |
checkout_url |
Send the customer here. Single use: once paid, it shows "already paid". |
success_url, cancel_url |
Where the customer is sent after paying (a *Return to … * button, with ?payment_id=pay_… added) and where the Cancel link on the checkout page goes. |
expires_at |
After this time the link no longer takes payments. Default 24 hours after creation. |
customer |
The name and email you gave, or once paid, the ones the customer typed at checkout. |
charge |
Once paid: { "id": "…", "receipt_url": "…", "currency": "USD", "amount": 5498, "exchange_rate": 1.0996, "exchange_rate_at": "…" }. exchange_rate is USD per 1 unit of your currency, and is null for USD payments. receipt_url is the customer's receipt page. |
seller_earning |
Once paid: { "currency": "USD", "amount": 4900 }, what you earn from this payment. |
source |
api or dashboard (payments you made by hand in the seller panel appear here too). |
Create a payment: POST /payments
| Parameter | Type | Required | Notes |
|---|---|---|---|
amount |
integer | yes | Minor units, at least 1. Must convert to between $5.00 and $4,850.00. |
currency |
string | yes | Three-letter code from the supported list. Case-insensitive. |
description |
string | yes | Max 120 characters. |
customer_email |
string | no | Pre-fills the checkout form. |
customer_name |
string | no | Pre-fills the checkout form. |
reference |
string | no | Your order id. Max 255. |
metadata |
object | no | Up to 20 keys (≤ 40 chars) with string values (≤ 500 chars). |
success_url |
string | no | https://… address to send the customer back to. |
cancel_url |
string | no | https://… address for the Cancel link. |
expires_in |
integer | no | Seconds the link stays open: 300 to 2592000. Default 86400. |
curl https://itamaster-pay.com/api/v1/payments \
-H "Authorization: Bearer $PAY_API_KEY" \
-H "Idempotency-Key: order-1042" \
-H "Content-Type: application/json" \
-d '{
"amount": 4999,
"currency": "EUR",
"description": "Order #1042",
"reference": "order-1042",
"customer_email": "jane@example.com",
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"metadata": { "cart_id": "abc123" }
}'
Returns 201 with the payment object. Redirect the customer to checkout_url.
Retrieve a payment: GET /payments/{id}
Returns the payment object, or 404. Use it to confirm the outcome when a customer returns to your site, and from your webhook handler before you act.
List payments: GET /payments
Every invoice you made, whether through the API, the seller panel or the WordPress plugin, with its status. Query parameters (all optional, combine freely):
| Parameter | Meaning |
|---|---|
status |
open, paid, cancelled or expired. |
reference |
Exactly your reference, such as an order number. |
currency |
The invoice currency, e.g. EUR. |
customer_email |
Payments for this email address. |
q |
Search the payment id, description, reference, customer name and email. |
created_after, created_before |
A date or date-time, e.g. 2026-10-01 or 2026-10-01T00:00:00Z. |
limit |
1 to 100, default 10. |
starting_after |
A payment id, for the next page. |
Newest first.
{ "object": "list", "url": "/api/v1/payments", "has_more": true, "total_count": 42, "data": [ { "id": "pay_…" } ] }
total_count is how many payments match your filters. To read everything, repeat the call with starting_after set to the last id until has_more is false.
Summary for a period: GET /payments/summary
Takes the same filters as the list (except status) and answers "how did I do?" in one call:
{
"object": "payment_summary",
"count": 42,
"by_status": { "open": 3, "paid": 36, "cancelled": 2, "expired": 1 },
"paid": {
"count": 36,
"invoiced_by_currency": { "EUR": 120000, "USD": 45000 },
"charged_usd": 178500,
"earned_usd": 160650
},
"filters": { "created_after": "2026-10-01" }
}
invoiced_by_currency is what you invoiced, per currency, in minor units. charged_usd is what customers were charged in USD, and earned_usd is what you keep after fees.
Update a payment: PATCH /payments/{id}
Changes a payment that is still open or expired. Send only what should change: description, customer_name, customer_email, reference, metadata, success_url, cancel_url, expires_in. Sending expires_in re-opens an expired payment for that many more seconds. The amount and currency cannot change: cancel the payment and create a new one. A paid payment returns 409 payment_already_paid, a cancelled one 409 payment_cancelled.
Cancel a payment: POST /payments/{id}/cancel
Stops an open payment from taking money; the checkout link then shows as unavailable. Cancelling twice is harmless. A payment that is already paid returns 409 payment_already_paid. Sends the payment.cancelled webhook.
7. Webhooks
A webhook is an HTTPS POST to your server whenever something happens to one of your payments. Webhooks are how you reliably learn that a customer paid.
Add endpoints in the seller panel under Developers → Webhooks, or with the API (section 8). Each endpoint has its own signing secret (whsec_…) and can listen to some events or all of them.
Events
| Event | Sent when |
|---|---|
payment.created |
A payment is created (through the API or by hand in the seller panel). |
payment.paid |
The customer's payment was confirmed. Fulfil the order on this event. |
payment.cancelled |
An unpaid payment was cancelled. |
webhook.test |
You pressed Send test event in the panel. |
The request you receive
POST /your/webhook/path HTTP/1.1
Content-Type: application/json
User-Agent: PayGateway-Webhooks/1.0
X-Pay-Event: payment.paid
X-Pay-Event-Id: evt_01k7x3…
X-Pay-Delivery-Attempt: 1
X-Pay-Signature: t=1760176800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"id": "evt_01k7x3b6c9d2e4f8g1h5j7k0mn",
"object": "event",
"type": "payment.paid",
"created": 1760176800,
"data": { "object": { "id": "pay_…", "object": "payment", "status": "paid", "amount": 4999, "currency": "EUR", "reference": "order-1042", "charge": { "currency": "USD", "amount": 5498, "exchange_rate": 1.0996 } } }
}
data.object is the full payment object from section 6. id is unique per event: use it to ignore duplicates, because the same event can be delivered more than once.
Verify the signature (always)
Anyone can POST to your URL, so check that the request really came from ITA Master Pay:
- Read the raw request body exactly as received (before any JSON parsing or re-encoding).
- Read the
X-Pay-Signatureheader:t=<unix time>,v1=<hex signature>. - Compute
HMAC-SHA256of the string"<t>.<raw body>"using your endpoint's secret (whsec_…) as the key, as lowercase hex. - Compare with
v1using a constant-time comparison. - Reject the request if
tis more than 5 minutes from your clock (this stops replays of captured requests).
Node.js (Express):
const crypto = require('crypto');
// Use express.raw so the body stays untouched: app.post('/webhooks/pay', express.raw({ type: 'application/json' }), handler)
function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!parts.t || !parts.v1 || age > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
PHP:
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_PAY_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $parts); // t=…&v1=…
$expected = hash_hmac('sha256', ($parts['t'] ?? '') . '.' . $raw, $secret);
if (! isset($parts['t'], $parts['v1'])
|| abs(time() - (int) $parts['t']) > 300
|| ! hash_equals($expected, $parts['v1'])) {
http_response_code(400);
exit;
}
$event = json_decode($raw, true);
Python:
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if "t" not in parts or "v1" not in parts or abs(time.time() - int(parts["t"])) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Reply and retries
- Reply with any
2xxstatus within 8 seconds. Do slow work after replying (queue it). - Any other answer, a timeout or a connection error counts as a failure and the event is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours (7 attempts in total). After that it is marked failed; you can resend it from Developers → Webhooks → (endpoint) → Resend.
- Redirects are not followed. The URL must be
https://, on a public address (notlocalhostor a private network). - Deliveries are not guaranteed to arrive in order. Fetch the payment (
GET /payments/{id}) if you need the current state.
A safe handler, step by step
- Verify the signature. Reject with
400if it fails. - Parse the JSON. If you have already processed
id(the event id), reply200and stop. - On
payment.paid: look up your order bydata.object.reference(ormetadata), check thatamountandcurrencymatch what you expect, then mark it paid and fulfil it. For high-value orders, also callGET /payments/{id}and requirestatus=paid. - Reply
200.
8. Webhook endpoints API
Manage endpoints from code (the seller panel does the same). The secret is returned only when an endpoint is created.
| Method and path | Purpose |
|---|---|
POST /webhook-endpoints |
Create. Body: url (required), events (array, default all), description, enabled. Returns 201 including secret. |
GET /webhook-endpoints |
List your endpoints. |
GET /webhook-endpoints/{id} |
Retrieve one (we_…). |
PATCH /webhook-endpoints/{id} |
Change url, events, description, enabled. |
DELETE /webhook-endpoints/{id} |
Delete. |
POST /webhook-endpoints/{id}/test |
Send a test event now. Returns the delivery with your server's answer. |
POST /webhook-endpoints/{id}/rotate-secret |
Create a new signing secret (returned once). The old one stops verifying at once. |
GET /webhook-endpoints/{id}/deliveries |
What was sent and how your server answered, newest first. Filters: status (pending, delivered, failed), limit, starting_after. |
POST /webhook-endpoints/{id}/deliveries/{id}/resend |
Try one delivery again right now. The second {id} is the delivery's evt_… id. |
A delivery looks like { "id": "evt_…", "object": "webhook_delivery", "endpoint": "we_…", "event": "payment.paid", "status": "delivered", "attempts": 1, "last_status_code": 200, "last_error": null, "next_attempt_at": null, "delivered_at": "…", "created_at": "…" }.
curl https://itamaster-pay.com/api/v1/webhook-endpoints \
-H "Authorization: Bearer $PAY_API_KEY" -H "Content-Type: application/json" \
-d '{ "url": "https://shop.example.com/webhooks/pay", "events": ["payment.paid", "payment.cancelled"] }'
# => { "id": "we_…", "secret": "whsec_…", "events": ["payment.paid","payment.cancelled"], "enabled": true, ... }
You can have at most 10 endpoints. Valid event names: payment.created, payment.paid, payment.cancelled.
9. Account, balance, payouts and currencies
| Request | Returns |
|---|---|
GET /account |
{ "object": "account", "id", "name", "email", "created_at", "api_version": "v1", "api_key": { "name", "last_four", "created_at" } }. A quick check that your key works. |
GET /payouts |
Your withdrawal requests, newest first. Filters: status (pending, approved = paid out, rejected, cancelled), limit, starting_after. |
GET /payouts/{id} |
One payout (WD-00012): { "id", "object": "payout", "status", "amount", "currency": "USD", "method", "note", "admin_note", "reference", "created_at", "processed_at" }. |
GET /balance |
{ "object": "balance", "currency": "USD", "available", "pending_withdrawals", "withdrawn", "total_earned", "sales_count" }, all in USD cents. Withdrawals are requested in the seller panel. |
GET /currencies |
Every supported currency with usd_rate (USD value of one unit, live) and today's min_amount / max_amount in that currency's minor units. |
Requesting a payout is deliberately not in the API: it stays in the seller panel, so a leaked API key can never move money out.
10. WordPress and WooCommerce
No code needed for WordPress sites: download the ITA Master Pay plugin from the seller panel (Developers → WordPress plugin), install it under Plugins → Add New → Upload, and paste your API key.
- WooCommerce: adds ITA Master Pay as a payment method at checkout (classic and block checkout). Orders are marked paid automatically by webhook.
- Invoices from your WordPress dashboard: a ITA Master Pay Invoices screen lets you create an invoice, copy its pay link and email it to your customer, like the invoice screen in the seller panel. Your customer pays through the link; they need no account or key.
- Any WordPress page: the shortcode
[pay_gateway_button amount="25.00" currency="EUR" description="Consulting hour" label="Pay now"]shows a pay button for a fixed amount.
11. Integration checklist
- The secret key is in an environment variable on the server, not in code or the browser.
- Payments are created with an
Idempotency-Keyand yourreference. - The customer is redirected to
checkout_url. - A webhook endpoint exists, the signature is verified on the raw body, and duplicates are ignored by event
id. - Orders are fulfilled on
payment.paid, after checkingamountandcurrency, not on the redirect tosuccess_url. - Your handler replies
2xxquickly and does slow work afterwards. - You tested with a real small payment (minimum $5.00) and cancelled unused test payments with
POST /payments/{id}/cancel.
Common mistakes
- Sending amounts in major units (
49.99) instead of minor units (4999). - Parsing the webhook body to JSON and re-serialising it before verifying the signature. It must be the exact bytes received.
- Marking an order paid from the browser redirect.
- Putting the secret key in front-end code.
- Using a currency that is not on the supported list, or an amount outside the $5.00 to $4,850.00 range once converted.
- Expecting the customer to be charged in the invoice currency: they are charged in USD at the live rate (see
charge).