Skip to content

Airwallex

Setup guide for the airwallex payments provider. The shared API (initiate_payment, verify_payment, refund_payment, webhooks, hooks, saved methods) is documented in Payments.

The airwallex provider supports one-time payments through Airwallex Payment Links (initiate_payment, verify_payment; cards and the local payment methods your account enables), refunds (refund_payment), reading disputes and webhooks, in the currencies Airwallex acquires (AUD, HKD, SGD, CNY, GBP, EUR, USD, …). GET /payments/providers reports dispute_read.

Feature Status
One-time payments (Payment Links), verify, refunds Supported
get_dispute, dispute webhooks Supported
submit_dispute_evidence Not supported (NOT_SUPPORTED, 501): respond in the Airwallex web app
Subscriptions, billing portal Not supported (NOT_SUPPORTED, 501)
Saved payment methods, charge_off_session Not supported yet (NOT_SUPPORTED, 501)

It needs no extra install: it calls Airwallex’s REST API directly over HTTP (https://api-demo.airwallex.com in sandbox, https://api.airwallex.com in production), following Airwallex’s API reference as of September 2026.

Why plain HTTP (no Airwallex SDK). Airwallex publishes no Python SDK; its official server SDK is for Node.js.

Access token. Every call carries a bearer token from POST /api/v1/authentication/login (x-client-id, x-api-key), cached per instance until two minutes before its expires_at (about 30 minutes) and fetched again once when a call answers 401.

1. Get credentials. In the Airwallex web app, Developer -> API keys, copy the Client ID and create an API key (a sandbox account has its own).

2. Set the webhook. In Developer -> Webhooks, add a notification URL https://your-domain.com/payments/webhook/airwallex (/payments/webhook/<instance> for another instance name), subscribe to payment_link.paid, refund.accepted, refund.settled and the payment_dispute.* events, and copy the URL’s secret.

3. Configure the instance and secrets. In kirak.json:

"airwallex": { "type": "airwallex", "environment": "sandbox" }

environment is sandbox (the default, the demo host) or production. In the environment:

Terminal window
KIRAK_PAYMENT_AIRWALLEX_CLIENT_ID=...
KIRAK_PAYMENT_AIRWALLEX_API_KEY=...
KIRAK_PAYMENT_AIRWALLEX_WEBHOOK_SECRET=<the notification URL's secret>

For an instance with another name the variables are KIRAK_PAYMENT_<INSTANCE>_CLIENT_ID, _API_KEY and _WEBHOOK_SECRET. All three are required.

4. Use it:

result = await kirak.payments.initiate_payment({
"provider": "airwallex",
"user_id": "42", "amount": 16515, "currency": "EUR", # cents: EUR 165.15
"name": "Pro Plan", "type": "one_time",
})
reference = result["data"]["reference"] # the link's reference, kirak-<32 hex>
await kirak.payments.verify_payment({"provider": "airwallex", "payment_id": reference})
await kirak.payments.refund_payment({"provider": "airwallex", "payment_id": reference, "amount": 6515})

Payments. initiate_payment stores a random reference (kirak-<32 hex>) on the PENDING row before creating a single-use payment link (title = name, amount x quantity, the reference, metadata naming the transaction and instance), then keeps the link id on the row and returns the link’s url. A 4xx answer marks the row FAILED; a timeout, 5xx or 429 leaves it PENDING.

payment_link.paid is never trusted alone: the link is re-read (it must carry the row’s reference and stored link id, be PAID and have metadata that, where present, names this transaction and instance), then its latest_successful_payment_intent_id is read; the row completes once (payment_completed, with amount and currency) only for a SUCCEEDED intent in the row’s currency for at least amount x quantity. Otherwise nothing is written and the webhook fails (500), so Airwallex retries. A row whose create call timed out (so no link id was stored) is completed from the event’s link, re-read the same way. verify_payment sets PROCESSING for a confirmed payment.

Refunds. refund_payment (payment_id = the reference; optional amount in minor units and reason) calls POST /api/v1/pa/refunds/create for the completing payment intent, with metadata naming the reference and instance and a request_id derived from the caller’s idempotency_key and the transaction’s random reference (random without one), so a retry with the same key never refunds twice. A refund Airwallex answers ACCEPTED or SETTLED is recorded at once; one RECEIVED is recorded by the refund.accepted/refund.settled webhook, which re-reads the refund and finds the row through that metadata (keyed by refund id with a running total; REFUNDED never moves back; refund_completed). Refunds made in the Airwallex web app carry no such metadata and are not recorded in Kirak.

Disputes (read-only). get_dispute reads GET /api/v1/pa/payment_disputes/{id}. A payment_dispute.* webhook re-reads the dispute and its payment intent and reports it (dispute_created for payment_dispute.requires_response, else dispute_updated) for the row that intent completed, found through the intent’s metadata or merchant_order_id naming the reference; the row is not changed.

Webhook security. x-signature must be the hex HMAC-SHA256, with the notification URL’s secret, of x-timestamp (milliseconds) followed by the raw body; a missing header is MISSING_WEBHOOK_SIGNATURE, a wrong one INVALID_WEBHOOK_SIGNATURE (400). No time window is set: Airwallex does not document whether a retry is signed again. Deliveries are deduplicated on the event id.

Currencies. Airwallex amounts are major units, sent as JSON numbers built exactly from the decimal, with ISO 4217 decimals.

Known limits.

  • Subscriptions, saved payment methods and off-session charges are not supported yet.
  • Dispute webhooks are reported only when Airwallex copies the link’s reference to the payment intent (metadata or merchant_order_id); this is not documented and is on the sandbox checklist.
  • Refunds made in the Airwallex web app are not recorded.

after_webhook receives these provider-neutral event names (see Webhook Hooks):

Airwallex reports payment_completed (payment_link.paid, once the re-read link and payment intent confirm it), refund_completed (refund.accepted/refund.settled of a Kirak refund, event_type refund.recorded), dispute_created (payment_dispute.requires_response) and dispute_updated (other payment_dispute.*, event_type payment_dispute.updated).