Unofficial Python SDK for the Airwallex API — payouts, FX, balances, global accounts, beneficiaries, deposits, 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 the same developer experience to Python:
- Sync and async clients (
Airwallex/AsyncAirwallex) built on httpx - Automatic authentication — token fetched on first use and refreshed before expiry; no manual login calls
- Idempotent by default —
request_idis auto-generated for money-moving calls, so retries never double-pay - Automatic retries with full-jitter exponential backoff on 408/429/5xx/network failures (honours
Retry-After; 409 business conflicts are never retried) - Typed responses — Pydantic v2 models that are immutable and forward-compatible (unknown fields preserved, never dropped)
- Auto-pagination — iterate every page with one loop
- Webhook signature verification with replay protection
- Typed errors —
RateLimitError,AuthenticationError, … each carrying the Airwallex errorcode,source, andrequest_id
Available on PyPI:
pip install airwallexRequires Python 3.9+. Releases follow semantic versioning; see the changelog.
Create API credentials in the Airwallex web app under Developer → API keys, then:
from airwallex import Airwallex
client = Airwallex(
client_id="your_client_id", # or set AIRWALLEX_CLIENT_ID
api_key="your_api_key", # or set AIRWALLEX_API_KEY
environment="demo", # "production" (default) or "demo" sandbox
)
# Current wallet balances
for balance in client.balances.current():
print(balance.currency, balance.available_amount)Payouts use
/api/v1/transfers, which requires API version 2024-01-31 or later. If your account default is older, passapi_version="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",
)
print(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(buy_currency="USD", sell_currency="SGD", buy_amount=1000)
print(rate.client_rate)
conversion = client.conversions.create(
buy_currency="USD",
sell_currency="SGD",
buy_amount=1000,
term_agreement=True,
)intent = client.payment_intents.create(
amount=25.00,
currency="USD",
merchant_order_id="order_42",
)
confirmed = client.payment_intents.confirm(intent.id, payment_method={"type": "card", "card": {...}})
refund = client.refunds.create(payment_intent_id=intent.id, amount=5.00)cardholder = client.issuing_cardholders.create(
email="employee@example.com",
individual={"name": {"first_name": "Ada", "last_name": "Lovelace"}},
type="INDIVIDUAL",
)
card = client.issuing_cards.create(
cardholder_id=cardholder.cardholder_id,
form_factor="VIRTUAL",
created_by="Ada Lovelace",
program={"purpose": "COMMERCIAL"},
)client.simulation.create_deposit(amount=1000, currency="USD") # demo environment only
client.simulation.transition_transfer("tra_123", next_status="PAID")# Iterates page by page under the hood
for beneficiary in client.beneficiaries.list(page_size=100).auto_paging_iter():
print(beneficiary.beneficiary_id, beneficiary.nickname)import asyncio
from airwallex import AsyncAirwallex
async def main() -> None:
async with AsyncAirwallex(environment="demo") as client:
page = await client.transfers.list(status="PAID")
async for transfer in page.auto_paging_iter():
print(transfer.id)
asyncio.run(main())Verify and parse incoming notifications (get the secret when you create the webhook endpoint):
from airwallex import webhooks, WebhookSignatureError
def handle(request): # any web framework
try:
event = webhooks.construct_event(
payload=request.body, # raw bytes — do not re-serialise
timestamp=request.headers["x-timestamp"],
signature=request.headers["x-signature"],
secret=WEBHOOK_SECRET,
)
except WebhookSignatureError:
return 400
if event.name == "transfer.settled":
...
return 200from airwallex import Airwallex, RateLimitError, APIStatusError
try:
client.transfers.retrieve("tra_missing")
except RateLimitError:
... # already retried automatically; back off further
except APIStatusError as err:
print(err.status_code, err.code, err.message, err.request_id)All API errors inherit from airwallex.APIError; network failures raise airwallex.APIConnectionError.
Every list method accepts extra query params as keyword arguments, and the client exposes a raw escape hatch with auth, retries, and error mapping intact:
page = client.transfers.list(short_reference_id="REF123") # extra filter
disputes = client.request("GET", "/api/v1/pa/payment_disputes", params={"status": "OPEN"})Response models live in airwallex.types and preserve unknown fields for forward compatibility:
from airwallex.types import Transfer, Balance
def settle(transfer: Transfer) -> None: ...import httpx
client = Airwallex(http_client=httpx.Client(proxy="http://proxy:3128"))The SDK applies the base URL and default headers per request, so proxies and custom TLS work without extra configuration; the SDK will not close a client you own.
client = Airwallex(on_behalf_of="acct_connected_account_id") # sets x-on-behalf-ofclient = Airwallex(api_version="2024-01-31") # sets x-api-version on every requestTwo quirks observed against the live demo API, worth knowing:
- Airwallex versions endpoint groups independently — e.g.
conversions.listrequires2024-01-31on some accounts whilefx/rates/currentrejects it. If you hitincorrect_versionerrors, use a second client pinned differently for that endpoint group. - Several list endpoints enforce a minimum
page_sizeof 10 and returninvalid_argumentbelow it. - Payment-acceptance lifecycle actions (
payment_intents.confirm/capture/cancel,customers.update) require arequest_id— the SDK auto-generates one, like it does for creates. webhook_endpoints.createrequires aversionfield (e.g.version="2024-01-31").- Newer API versions rename beneficiary fields (
beneficiary_id→id,payment_methods→transfer_methods); theBeneficiarymodel types both.
| Resource | Methods |
|---|---|
client.balances |
current, history |
client.transfers |
create, retrieve, list, cancel, validate, confirm_funding |
client.batch_transfers |
create, retrieve, list, add_items, delete_items, items, quote, submit, delete |
client.wallet_transfers |
create, retrieve, list |
client.payers |
create, retrieve, update, delete, list, validate |
client.beneficiaries |
create, retrieve, update, delete, list, validate |
client.conversions |
create, retrieve, list |
client.rates |
current |
client.fx_quotes |
create, retrieve |
client.conversion_amendments |
create, quote, retrieve, list |
client.payment_intents |
create, retrieve, list, confirm, confirm_continue, capture, cancel |
client.customers |
create, retrieve, update, list, generate_client_secret |
client.refunds |
create, retrieve, list |
client.issuing_cardholders |
create, retrieve, update, delete, list |
client.issuing_cards |
create, retrieve, update, activate, limits, list |
client.issuing_transactions |
retrieve, list |
client.issuing_authorizations |
retrieve, list |
client.accounts |
retrieve |
client.financial_transactions |
retrieve, list |
client.settlements |
retrieve, list |
client.simulation |
demo-only: deposit create/settle/reject/reverse, transfer/payment transitions |
client.global_accounts |
create, retrieve, update, close, list, transactions |
client.deposits |
list |
client.reference |
supported_currencies, settlement_accounts, invalid_conversion_dates |
client.webhook_endpoints |
create, retrieve, update, delete, list |
airwallex.webhooks |
verify_signature, construct_event |
Coverage now matches (and in webhooks/pagination/simulation, exceeds) the official Node.js SDK's main surfaces — contributions welcome for the remaining areas (disputes, payment consents, linked accounts, scale/platform APIs).
This SDK is beta software:
- The wrapped endpoints are grounded in Airwallex's published API spec and covered by tests, but they have not yet been exercised against every account configuration.
- Semantic versioning applies: breaking changes only in minor versions while
0.x, and patch releases never change behavior. - Response models tolerate unknown fields, so new Airwallex API versions won't break parsing.
- Test in the
demoenvironment before pointing at production, and pin the version in your dependency file (e.g.airwallex==0.2.0).
Runnable scripts for every major flow — payments, issuing, FX, webhooks, async, error handling — live in examples/.
uv sync # install dependencies
uv run pytest # run tests
uv run ruff check . # lint
uv run mypy # type-checkThis project is an independent, unofficial SDK maintained by the community. It is not affiliated with, endorsed by, sponsored by, or supported by Airwallex Pty Ltd. "Airwallex" and related marks are trademarks of Airwallex Pty Ltd; they are used here solely to indicate API compatibility. This software is provided "as is" under the MIT license — review the SECURITY policy and test against the demo environment before moving real money. If you need vendor support or SLAs, use the official Node.js SDK.