Xero and Brex integration
Xero runs accounting for small and medium businesses. Brex runs corporate cards, expenses, and bill pay. Connecting the two means the card spend and bills that Brex codes flow into Xero without re-keying, and the vendor and account masters stay in agreement. ml-connector handles the different APIs on each side and moves the data as Brex finishes coding each transaction. Because Brex card and cash transactions are read-only, the general ledger stays in Xero where it belongs.
What moves between them
The main flow runs from Brex into Xero. Once Brex marks an accounting record or coded card expense ready for export, ml-connector reads it and posts it into Xero as a bill of type ACCPAY or a manual journal, mapped to the matching Xero account and contact. Vendor records flow the same direction so Xero supplier contacts reflect the Brex vendor directory. Reference data such as the Brex chart of accounts mapping and accounting fields is aligned with Xero accounts and tracking categories so each line lands on a valid dimension. Brex card and cash transactions are read-only, so ml-connector never writes ledger entries back into Brex; it only marks each record exported.
How ml-connector handles it
ml-connector stores both credential sets encrypted. On the Brex side it sends the bearer token on every request and tracks the User Token, since it expires after 90 days without a call, so a heartbeat keeps it alive. On the Xero side it refreshes the 30-minute access token before expiry, renews the 60-day refresh token on use, and sends the Xero-tenant-id header so every post lands in the right organization. The trigger is the Brex ACCOUNTING_RECORD_READY_FOR_EXPORT webhook, verified with its Svix headers, after which ml-connector calls GET on the accounting record to read the coded transaction, then posts it into Xero. A scheduled poll over Brex records and expenses backfills anything a webhook missed, paging by next_cursor and capping the Expenses API at 100 per page after the February 2026 limit change. Accounts and tracking categories are mapped first, so every line references a Xero account and a contact that already exist. Brex merchant names rarely match Xero contact names exactly, so a fuzzy-match and caching layer resolves each merchant to the right supplier. Neither side offers a Stripe-style idempotency header, so ml-connector dedupes on the Brex record id and the Xero GUID before posting, supplying the GUID on create as an upsert, then calls Xero, and on success marks the Brex record EXPORTED. Brex rate limits return HTTP 429 with no documented headers, so ml-connector backs off with jitter, and every record carries a full audit trail and can be replayed if a Xero post fails.
A real-world example
A mid-sized software firm of about 120 people runs Xero for accounting and gives staff Brex corporate cards for software, travel, and vendor bills. Before the integration, an accountant exported card statements from Brex each week and keyed the coded spend into Xero by hand, which meant expenses sat uncategorized for days, merchant names were typed differently each time, and the card clearing account never tied out at month-end. With Xero and Brex connected, each coded card expense and bill posts into Xero as it becomes ready for export, allocated to the right account and tracking category, and vendors stay aligned with Xero contacts. Close starts with the card spend already booked, and the weekly re-keying step is gone.
What you can do
- Post Brex coded card expenses and accounting records into Xero as bills or manual journals once they are ready for export.
- Keep Xero supplier contacts aligned with the Brex vendor directory using fuzzy merchant matching.
- Map Brex accounting fields to Xero accounts and tracking categories so each line lands on a valid dimension.
- Authenticate Brex with its bearer token and Xero with OAuth 2.0 and the per-organization tenant header.
- Trigger on the Brex ready-for-export webhook with scheduled backfill, dedup, retries, and a full audit trail on every record.
Questions
- Which direction does data move between Xero and Brex?
- The main flow is Brex into Xero. Coded card expenses, accounting records, and vendors move from Brex into Xero as bills, manual journals, and supplier contacts. Brex card and cash transactions are read-only, so the general ledger stays in Xero and ml-connector only marks each Brex record exported, never writing ledger entries back.
- How does the integration know when a Brex transaction is ready to post?
- Brex pushes the ACCOUNTING_RECORD_READY_FOR_EXPORT webhook once it finishes coding a transaction. ml-connector verifies the Svix signature, then calls GET on the accounting record to read the coded values and posts it into Xero. A scheduled poll over Brex records and expenses backfills anything a webhook missed, paging by cursor.
- How are duplicate postings avoided without a shared idempotency key?
- Neither Xero nor Brex offers a Stripe-style idempotency header for these records. ml-connector dedupes on the Brex record id and the Xero GUID, supplying the GUID on create so Xero treats the call as an upsert. After Xero accepts the post it marks the Brex record EXPORTED, so a re-read transaction is never booked twice.
Related integrations
More Xero integrations
Other systems that connect to Brex
Connect Xero and Brex
Free to use. Add your credentials, ping your real systems, and see if we fit.
Get started