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.

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                                                |
  1. Create a payment with an amount, a currency and a description. You get back a checkout_url.
  2. 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.
  3. Wait for the payment.paid webhook (or fetch the payment) and only then deliver the goods or mark the order as paid. Never trust the redirect to success_url alone: a customer can type that address by hand.

Money and currencies

  • Amounts are integers in the smallest unit of the currency (cents). 4999 with currency USD is $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 /currencies returns 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_earning on 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://www.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://www.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:

  1. Read the raw request body exactly as received (before any JSON parsing or re-encoding).
  2. Read the X-Pay-Signature header: t=<unix time>,v1=<hex signature>.
  3. Compute HMAC-SHA256 of the string "<t>.<raw body>" using your endpoint's secret (whsec_…) as the key, as lowercase hex.
  4. Compare with v1 using a constant-time comparison.
  5. Reject the request if t is 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 2xx status 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 (not localhost or 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

  1. Verify the signature. Reject with 400 if it fails.
  2. Parse the JSON. If you have already processed id (the event id), reply 200 and stop.
  3. On payment.paid: look up your order by data.object.reference (or metadata), check that amount and currency match what you expect, then mark it paid and fulfil it. For high-value orders, also call GET /payments/{id} and require status = paid.
  4. 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://www.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-Key and your reference.
  • 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 checking amount and currency, not on the redirect to success_url.
  • Your handler replies 2xx quickly 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).