Unofficial PHP SDK for the Airwallex API — payouts, FX, balances, global accounts, beneficiaries, payments, issuing, and webhooks.
Important
This is an unofficial, community-maintained library. It is not affiliated with, endorsed by, or supported by Airwallex Pty Ltd — "Airwallex" is their trademark, used here only to describe compatibility. The SDK is in beta: the public interface may change before v1.0, so pin your version. For vendor-supported tooling, use the official Node.js SDK.
Airwallex's only official server-side SDK is Node.js. This library brings a Stripe-quality developer experience to PHP:
- Modern PHP client (PHP 8.1+,
strict_types, named arguments) built on Guzzle 7, with any PSR-18 client injectable - Automatic authentication — token fetched on first use, cached on the client, and refreshed before expiry; no manual login calls
- Idempotent by default —
request_idis auto-generated (UUIDv4) for money-moving calls, so retries never double-pay - Automatic retries with full-jitter exponential backoff on 408/429/5xx/network failures (honours
Retry-Afterin both seconds and HTTP-date form; 409 business conflicts are never retried) - Typed responses — immutable value objects that are forward-compatible (unknown fields preserved, never dropped)
- Auto-pagination — iterate every page with one loop
- Webhook signature verification with constant-time comparison and replay protection
- Typed exceptions —
RateLimitException,AuthenticationException, … each carrying the Airwallex errorcode,source, andrequest_id - Never leaks credentials — API keys and tokens are redacted from
var_dump(), serialized state, and every exception
composer require pastelero/airwallexRequires PHP 8.1+ and ext-json. Releases follow semantic versioning; see the changelog.
Create API credentials in the Airwallex web app under Developer → API keys, then:
(runnable versions of every snippet below live in examples/)
use Airwallex\Client;
use Airwallex\Env;
$client = new Client(
clientId: 'your_client_id', // or set AIRWALLEX_CLIENT_ID
apiKey: 'your_api_key', // or set AIRWALLEX_API_KEY
env: Env::Demo, // Env::Production (default) or Env::Demo sandbox
);
// Current wallet balances
foreach ($client->balances->current() as $balance) {
echo $balance->currency, ' ', $balance->available_amount, PHP_EOL;
}Payouts use
/api/v1/transfers, which requires API version 2024-01-31 or later. If your account default is older, passapiVersion: '2024-01-31'(or newer) to the client.
$transfer = $client->transfers->create([
'beneficiary_id' => 'ben_abc123',
'source_currency' => 'USD',
'transfer_currency' => 'PHP',
'transfer_amount' => 5000,
'transfer_method' => 'LOCAL',
'reference' => 'Invoice 42',
'reason' => 'professional_service_fees',
]);
echo $transfer->id, ' ', $transfer->status;request_id is generated for you (pass your own to control idempotency). Airwallex will never execute the same request_id twice — including across the SDK's automatic retries.
$rate = $client->rates->current(buyCurrency: 'USD', sellCurrency: 'SGD', buyAmount: 1000);
echo $rate->rate; // indicative client rate; breakdown in $rate->rate_details
$conversion = $client->conversions->create([
'buy_currency' => 'USD',
'sell_currency' => 'SGD',
'buy_amount' => 1000,
'term_agreement' => true,
]);For a bookable rate, lock a quote first and pass its id to the conversion:
$quote = $client->fxQuotes->create([
'buy_currency' => 'USD',
'sell_currency' => 'SGD',
'buy_amount' => 1000,
'validity' => 'HR_1', // MIN_1|MIN_15|MIN_30|HR_1|HR_4|HR_8|HR_24
]);$intent = $client->paymentIntents->create([
'amount' => 25.00,
'currency' => 'USD',
'merchant_order_id' => 'order_42',
]);
$confirmed = $client->paymentIntents->confirm($intent->id, [
'payment_method' => ['type' => 'card', 'card' => [/* ... */]],
]);
$refund = $client->refunds->create(['payment_intent_id' => $intent->id, 'amount' => 5.00]);$cardholder = $client->issuingCardholders->create([
'email' => 'employee@example.com',
'individual' => ['name' => ['first_name' => 'Ada', 'last_name' => 'Lovelace']],
'type' => 'INDIVIDUAL',
]);
$card = $client->issuingCards->create([
'cardholder_id' => $cardholder->cardholder_id,
'form_factor' => 'VIRTUAL',
'created_by' => 'Ada Lovelace',
'program' => ['purpose' => 'COMMERCIAL'],
]);The PCI-scoped endpoints (/details, /provision_digital_token) are intentionally not implemented; card numbers returned by this SDK are masked.
use Airwallex\Webhooks;
use Airwallex\Exception\WebhookSignatureException;
try {
$event = Webhooks::constructEvent(
payload: file_get_contents('php://input'), // the RAW body — do not re-encode
timestamp: $_SERVER['HTTP_X_TIMESTAMP'],
signature: $_SERVER['HTTP_X_SIGNATURE'],
secret: $webhookSecret,
);
} catch (WebhookSignatureException $e) {
http_response_code(400);
exit;
}
if ($event->name === 'transfer.settled') {
// $event->data holds the resource payload
}Signatures are compared with hash_equals and deliveries older than 5 minutes are rejected as replays (configurable via toleranceSeconds).
List calls return a Page (countable, iterable). autoPagingIterator() walks every page lazily:
foreach ($client->beneficiaries->list()->autoPagingIterator() as $beneficiary) {
echo $beneficiary->beneficiary_id, PHP_EOL;
}
// or page manually
$page = $client->transfers->list(status: 'PAID', pageSize: 100);
if ($page->hasMore) {
$next = $page->nextPage();
}$client->simulation->createDeposit(['amount' => 1000, 'currency' => 'USD']); // demo env only
$client->simulation->transitionTransfer('tra_123', ['next_status' => 'PAID']);Call any endpoint the SDK has no wrapper for — authentication, retries, and error mapping still apply:
$disputes = $client->request('GET', '/api/v1/pa/payment_disputes', query: ['status' => 'OPEN']);You can also bring your own PSR-18 client (proxies, mTLS, observability); it is used as-is, never mutated or closed:
$client = new Client(httpClient: $myPsr18Client);use Airwallex\Exception\ApiException;
use Airwallex\Exception\ConflictException;
use Airwallex\Exception\RateLimitException;
try {
$client->transfers->create([/* ... */]);
} catch (ConflictException $e) {
// 409 — duplicate request_id or invalid state; never retried automatically
} catch (RateLimitException $e) {
// 429 — the SDK already retried with backoff and gave up
} catch (ApiException $e) {
echo $e->statusCode; // HTTP status
echo $e->errorCode; // Airwallex machine-readable code
echo $e->source; // offending field, when provided
echo $e->requestId; // quote this to Airwallex support
}ConnectionException covers transport failures; every SDK exception extends Airwallex\Exception\AirwallexException.
| Service | Methods |
|---|---|
$client->accounts |
retrieve |
$client->balances |
current, history |
$client->transfers |
create, retrieve, list, cancel, validate, confirmFunding |
$client->batchTransfers |
create, retrieve, list, addItems, deleteItems, items, quote, submit, delete |
$client->walletTransfers |
create, retrieve, list |
$client->beneficiaries |
create, retrieve, update, delete, list, validate |
$client->payers |
create, retrieve, update, delete, list, validate |
$client->conversions |
create, retrieve, list |
$client->rates |
current |
$client->fxQuotes |
create, retrieve |
$client->conversionAmendments |
create, quote, retrieve, list |
$client->globalAccounts |
create, retrieve, update, close, list, transactions |
$client->deposits |
list |
$client->paymentIntents |
create, retrieve, list, confirm, confirmContinue, capture, cancel |
$client->customers |
create, retrieve, update, list, generateClientSecret |
$client->refunds |
create, retrieve, list |
$client->issuingCardholders |
create, retrieve, update, delete, list |
$client->issuingCards |
create, retrieve, update, activate, limits, list |
$client->issuingTransactions |
retrieve, list |
$client->issuingAuthorizations |
retrieve, list |
$client->financialTransactions |
retrieve, list |
$client->settlements |
retrieve, list |
$client->reference |
supportedCurrencies, settlementAccounts, invalidConversionDates |
$client->webhookEndpoints |
create, retrieve, update, delete, list |
$client->simulation (demo only) |
createDeposit, settleDeposit, rejectDeposit, reverseDeposit, transitionTransfer, transitionPayment |
Request payloads are associative arrays matching the Airwallex API reference; list filters are named parameters plus an extraParams escape hatch for anything newer than this SDK.
The core SDK is deliberately framework-free. A thin pastelero/airwallex-laravel bridge (service provider, config/airwallex.php, facade, webhook middleware) is planned — see docs/laravel-bridge.md for the design.
Beta. The SDK follows semantic versioning; expect possible breaking changes in minor 0.x releases, so pin a minor version:
{ "require": { "pastelero/airwallex": "~0.1.0" } }Behavior is modelled on (and tested against the same matrix as) the airwallex-python SDK. Endpoint paths and parameters are grounded in the Airwallex OpenAPI schema and current documentation.
composer install
composer test # PHPUnit
composer stan # PHPStan (level max)
composer cs # php-cs-fixer (check only)
composer test:coverage # coverage report + cloverSee CONTRIBUTING.md and SECURITY.md.
MIT © Cyrus David Pastelero