Skip to main content

SDK

Official clients for TypeScript, Python and PHP. Same API in all three, same behaviour, same tests.

The SDK exists because the raw API has a handful of behaviours that are easy to get wrong and expensive when you do — a 201 Created that carries a refusal, a hosted flag with no default, timestamps that are not ISO 8601, a paging parameter that is accepted and ignored. Every one of those is handled here, and every one is documented in the code where you will meet it.


Install​

The packages are not on npm, PyPI or Packagist. Each installs from its own public repository — one per language, since npm and Composer read a manifest only from the root of a repository — or from the archive this site serves, for a build with no access to GitHub.

TypeScript / Node.js 18+​

npm install github:4pay-online/sdk-typescript

Or from the archive:

curl -O https://docs.4pay.online/sdk/4pay-sdk-typescript.tar.gz
npm install ./4pay-sdk-typescript.tar.gz

dist/ is committed, so neither install compiles anything.

Python 3.9+​

pip install "git+https://github.com/4pay-online/sdk-python.git"
# or: pip install https://docs.4pay.online/sdk/4pay-sdk-python.tar.gz

The SDK has no runtime dependencies. Mandate signature verification (AP2) needs one extra:

pip install "4pay-sdk[crypto] @ git+https://github.com/4pay-online/sdk-python.git"

PHP 8.1+​

Composer reaches the repository through a vcs entry:

{
"repositories": [
{ "type": "vcs", "url": "https://github.com/4pay-online/sdk-php.git" }
],
"require": { "4pay/sdk": "dev-master" }
}
composer install

Or unpack the archive and run composer install inside it. Requires ext-json, ext-curl and ext-bcmath.

Sources: github.com/4pay-online — TypeScript, Python and PHP, MIT. All three archives: docs.4pay.online/sdk/ — sources, examples and tests included.


First payment​

import { FourPay } from "@4pay/sdk";

const pay = new FourPay({
apiKey: process.env.FOURPAY_API_KEY,
organizationId: process.env.FOURPAY_ORGANIZATION_ID, // issued together with the key
baseUrl: "https://sandbox.4pay.online",
});

const { transaction, paymentUrl } = await pay.createHostedPayment({
type: "payment",
amount: "150.00", // or "15000" in minor units — both work
currency: "USD",
env: "test",
txid: "order-8891", // your order id, and the idempotency key
returnUrl: "https://shop.example/thanks",
failUrl: "https://shop.example/sorry",
});

redirect(paymentUrl); // valid for ~15 minutes
from fourpay import FourPay

client = FourPay(
api_key=os.environ["FOURPAY_API_KEY"],
organization_id=os.environ["FOURPAY_ORGANIZATION_ID"],
base_url="https://sandbox.4pay.online",
)

transaction, payment_url = client.create_hosted_payment(
amount="150.00", currency="USD", env="test", txid="order-8891",
returnUrl="https://shop.example/thanks", failUrl="https://shop.example/sorry",
)
$pay = new FourPay\Client(
apiKey: getenv('FOURPAY_API_KEY'),
organizationId: getenv('FOURPAY_ORGANIZATION_ID'),
baseUrl: 'https://sandbox.4pay.online',
);

[$transaction, $paymentUrl] = $pay->createHostedPayment([
'amount' => '150.00', 'currency' => 'USD', 'env' => 'test', 'txid' => 'order-8891',
'returnUrl' => 'https://shop.example/thanks', 'failUrl' => 'https://shop.example/sorry',
]);

What it handles for you​

Behaviour of the raw APIWhat the SDK does
hosted has no default; without it a payment is refused as unexpected cardcreateHostedPayment sets it
201 Created carries refusals (rejected, failed)Raises TransactionRejectedError, with isRoutingFailure to tell "no terminal" from "card declined"
A missing x-organization-id is refused with 401 on the payment endpoints and 400 on othersBoth headers always sent; a bare 400 or 401 surfaces as an auth error
Timestamps like 2026-09-03 11:18:30.101839 Etc/UTCparseTimestamp returns a correct UTC date in all three languages
Paging is cursor; start_cursor is silently ignorediterateTransactions follows the real cursor, and stops if it stops moving
page, offset, from, to, currency are refused with 422Not in the type definitions, so your editor refuses them first
A retried create without txid charges twiceWrites are only retried when they carry an idempotency key
Money as decimal strings in minor unitstoMinorUnits / fromMinorUnits, integer arithmetic throughout — never floats
Webhook signature over <timestamp>.<raw body>verifyWebhook — constant-time, replay-windowed, raw-bytes

Webhooks​

import { verifyWebhook, EventDeduplicator } from "@4pay/sdk";

const seen = new EventDeduplicator();

app.post("/webhooks/payment", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = verifyWebhook(req.body, req.headers, process.env.WEBHOOK_SECRET);
} catch {
return res.sendStatus(401);
}

res.sendStatus(200); // acknowledge first, work afterwards
if (!seen.accept(event.event_id)) return; // deliveries repeat

if (event.data.status === "charged") fulfil(event.data.txid);
});

The raw body matters: the signature covers bytes, and a parsed-then-re-serialised body has different ones. In Express that means express.raw() on this route; in Flask request.get_data(); in PHP file_get_contents('php://input').


Money​

toMinorUnits("10.00", "USD")   // "1000"
toMinorUnits("10.001", "USD") // throws — USD has two decimals, and rounding money is not ours to do
toMinorUnits(10.5, "USD") // throws — pass "10.50", not a float
fromMinorUnits("1000", "JPY") // "1000" — JPY has no minor unit
formatAmount("2500", "EUR") // "25.00 EUR"

An unknown currency raises rather than assuming two decimals: a guessed scale overstates JPY a hundredfold and ETH by sixteen orders of magnitude.


Sandbox fixtures​

import { sandbox } from "@4pay/sdk";

sandbox.CARD_CHARGE // settles
sandbox.CARD_REJECT // refused by the provider
sandbox.CARD_REFUND // settles, then refunds
sandbox.CARD_FAIL // fails

The simulator matches the whole tuple — number, CVV, holder, expiry — so these constants save you from a rejected caused by a lower-case cardholder name. See Sandbox & Testing.


Agentic commerce​

The SDK also carries the merchant side of the four protocols AI agents use to pay — MCP, AP2, ACP and A2A. That has its own page.


Examples​

The archive ships runnable examples:

ExampleWhat it shows
first-payment/The whole lifecycle end to end, in all three languages
webhook-server/A verified webhook endpoint, in all three languages
agent-tools/An AI agent taking payments through function calling
acp-merchant/An ACP checkout served from your own endpoints
a2a-agent/A payment agent other agents can hire
ap2-checkout/Verifying a mandate before charging

Support​

  • Source, issues and changelog: the archive's README.md
  • Platform questions: support@4pay.online — include the transaction id