Skip to main content

What breaks when Airwallex webhooks arrive out of order during payout reconciliation?

Airwallex does not guarantee webhook delivery in the order events were generated. A deposit.settled event can arrive before the deposit.pending event that should precede it, so reconciliation logic that assumes sequential arrival can build a match against incomplete state. Order by each event's created_at timestamp and deduplicate by its id, not by arrival order.

Zuny FesterBy Zuny Fester, Head of Operations and Marketing
Reviewed by Zuny Fester
Published Last reviewed Editorial policy

Part of the finance integrations guide.

SymptomA deposit shows as settled in Loopfour before its own deposit.pending event has been recorded
Root causeAirwallex webhooks are not delivered in generation order; network retries and parallel delivery can reorder events
What Airwallex's own docs say"Airwallex does not guarantee that events are delivered in the order they were generated"
Loopfour's registered event surfacepayment_attempt.paid, payment_attempt.settled, deposit.settled, deposit.pending — no payout-specific events are registered
What to key reconciliation off insteadThe event's created_at timestamp for ordering and its id field for deduplication across retries

Why webhook order isn't guaranteed

Airwallex's own webhook documentation states plainly that event delivery order is not guaranteed: network retries, parallel delivery workers, and transient failures on Airwallex's side can all cause a later event to arrive before an earlier one. Loopfour's Airwallex integration is event-driven — it has a full webhook handler pipeline (event verification, normalization, matching, registration) registered for four event types (payment_attempt.paid, payment_attempt.settled, deposit.settled, deposit.pending) rather than polling Airwallex's API on a schedule — which makes it fast, but also means reconciliation logic has to be built to tolerate events showing up in any order, not just the order they happened.

What this looks like in practice

A typical incoming-funds lifecycle emits at least two events: deposit.pending when Airwallex first sees the deposit, and deposit.settled once it clears. If the settled event's webhook delivery is retried (a transient timeout on your endpoint, for example) and redelivered after the pending event, or if the two events are processed by parallel workers on Airwallex's side, a reconciliation process that expects "pending" before "settled" can end up trying to match a settlement against a deposit record that doesn't exist yet — or, worse, silently skip the match and leave the settlement unreconciled. The same risk applies to payment_attempt.paid and payment_attempt.settled, the other registered pair.

How to build around it

Two fields on every Airwallex event solve this: created_at, which reflects when the event actually happened (not when it was delivered), and id, which stays the same across every retry of the same event. Sort or key reconciliation state by created_at rather than arrival time, and use id to detect and discard a duplicate delivery of an event you've already processed — Airwallex's own guidance is explicit that endpoints should implement idempotent handling keyed on id, since the same event can be delivered more than once. If an event was missed or processed incorrectly, Airwallex retains events for 30 days and lets you manually re-trigger a specific event's delivery from the dashboard rather than needing to rebuild state from scratch.

Next step

Map the finance workflow with the most exposure and prove the automation path.

Bring the invoice, contract, payment reconciliation, or customer finance workflow you have to defend at audit. Loopfour can map the trigger, controls, integrations, and approval loop.

Book a workflow review

Worked example

A deposit.settled event that arrives before its own deposit.pending

Airwallex records a deposit as pending at 14:02:01 UTC (event id evt_dep_pending_1, created_at 14:02:01) and marks it settled two seconds later at 14:02:03 UTC (event id evt_dep_settled_1, created_at 14:02:03). A transient timeout on the receiving endpoint causes Airwallex to retry the first event; by the time it successfully redelivers, the settled event has already arrived and been processed. A reconciliation process keyed on arrival order would see 'settled' before 'pending' and either error or silently drop the settlement. Keyed on created_at instead, the two events sort correctly regardless of which one physically arrived first — and if the settled event is somehow delivered twice due to the retry, its unchanged id (evt_dep_settled_1) lets the handler recognize and skip the duplicate.

Frequently Asked Questions

Airwallex retries failed deliveries — any response other than a 200 OK, or a timeout, is treated as a failed delivery and retried with exponential back-off for about three days. After that, an event can still be found and manually re-triggered from the dashboard for up to 30 days.

That reduces but doesn't eliminate the problem, and adds latency to every event. Ordering by created_at and designing matching logic to tolerate an out-of-sequence event (queue it, or retry the match on the next event) is more reliable than a fixed delay.

Loopfour's Stripe integration in this codebase doesn't use webhooks at all — it polls Stripe's API for balance and transaction data, which avoids the ordering problem entirely but trades it for polling latency. The two integrations fail differently because they're built differently, not because one vendor's webhooks are less reliable than the other's.

Sources

Related

Integration

Why don't my Stripe payouts tie to the NetSuite bank deposit?

A Stripe payout rarely equals one NetSuite deposit line: Stripe nets many charges, fees, and refunds into one transfer settled days later. Check, in order: the payout isn't unbundled into individual charges, the deposit date doesn't match the transaction date, fees are netted not booked separately, a partial capture created a variance, or the payout is still in Undeposited Funds.

Read more

Topic

Integrations

Every finance automation vendor publishes an integrations page: a grid of logos, a claim of "seamless" connectivity, and not much else. That page answers a marketing question — does this vendor touch…

Read more

Diagnostic

Why doesn't an uncommitted Avalara transaction show up on my sales tax return?

AvaTax only reports committed transactions. Commit status is a flag that determines whether a transaction appears on your compliance reports and gets remitted — uncommitted transactions are treated as provisional and are excluded from liability calculations and filings until someone explicitly commits them.

Read more

Diagnostic

What breaks when a reconciled Plaid transaction changes to modified or removed?

Plaid transaction data isn't immutable. A pending transaction can be removed from the feed entirely and replaced by a different transaction once it posts, and a bank can remove a transaction outright. If a payment was already matched to the old transaction, that match is orphaned the moment the removal comes through, and the replacement has to be found and re-matched — it doesn't update in place.

Read more