Xero to Attio Sync

Bring Xero customer, supplier, and invoice context into Attio without turning Attio into a second accounting system. This custom app pattern keeps Xero as the finance source of truth while giving sales, account, and client-service teams the billing context they need inside Attio. Contacts and invoices update through Xero webhooks, while backfills and on-demand resyncs use the Xero Accounting API.

Who this is for

This build is for teams that manage relationships in Attio and billing in Xero. It fits when account owners need to see whether a person or company is a customer, supplier, billing contact, or invoice owner without opening Xero for every question.

It is also a fit when finance data needs controls. We scope the exact Xero fields that enter Attio, keep tax and accounting-only data out by default, and preserve Xero identifiers such as ContactID and InvoiceID so records can be traced back to the finance system.

How the sync works

The sync has three paths: live webhook intake for new and changed records, Accounting API resync for any record that needs a fresh pull, and historical backfill for existing Xero data.

Webhook flow

Xero webhooks cover the standard contact and invoice lifecycle: CONTACT CREATE, CONTACT UPDATE, INVOICE CREATE, and INVOICE UPDATE. Each webhook event includes a Xero resource ID, event category, event type, event timestamp, tenantId, and tenantType.

The managed webhook gateway verifies the x-xero-signature, records each delivery attempt, retries transient failures, and supports replay during support. The Attio app then fetches the current contact or invoice from the Xero Accounting API before writing to Attio. The webhook tells the app that something changed; the API fetch supplies the current state.

Attio write shape

Customers and suppliers sync into Attio with the fields that matter for CRM use: Xero ContactID, ContactStatus, IsCustomer, IsSupplier, ContactNumber, and scoped billing-contact indicators. We typically do not mirror every Xero contact field.

Invoices usually land on a dedicated Attio surface for Xero invoices. The raw Xero fields we map use Xero's PascalCase API names, including InvoiceID, InvoiceNumber, ContactID, Type, Status, Date, DueDate, SubTotal, TotalTax, and Total.

Resync and backfill

Record actions in Attio can pull the latest Xero record on demand. Bulk and workspace-level flows use the same Xero Accounting API endpoints for larger refreshes and initial historical imports. Contacts and Invoices support page-based retrieval, page, pageSize, summaryOnly, and If-Modified-Since, so backfills can be paced without depending on webhook history.

What we configure during scope

The scoping call turns the Xero-to-Attio pattern into your build. These decisions are made before the app is installed.

Decision What we decide with you
Xero tenant Which authorized Xero tenant feeds the Attio workspace, and how multi-entity finance setups should route by tenantId.
Data coverage Whether the build covers customers, suppliers, invoices, credit notes, scheduled payment sync, repeating-invoice sync, or only a smaller subset.
Attio destination Which Attio People, Companies, lists, or custom objects should hold Xero-derived context.
Field map Which raw Xero fields, such as ContactID, InvoiceID, InvoiceNumber, SubTotal, TotalTax, and Total, should be exposed in Attio.
Data minimization Which accounting-only fields stay in Xero by default, including tax IDs unless a specific workflow needs them in Attio.
Backfill pacing How much historical data to import and how the queue should respect Xero tenant and app rate limits.
Support actions Which Attio record actions, bulk actions, replay controls, and reprovisioning controls should be available to operators.

Why this works with the Xero API

Xero exposes the pieces this pattern needs: app-level webhook configuration, signed webhook deliveries, tenant IDs in each event, OAuth 2.0 access for Accounting API calls, and documented Contacts and Invoices endpoints for backfill and resync.

The webhook side is documented in Xero API webhooks and the Xero Webhooks OpenAPI. Xero webhooks are configured in the Developer Portal for an app, not separately per tenant. Events for connected organisations include tenantId, which the receiver uses for tenant routing.

The API side uses the Accounting API Contacts and Invoices endpoints. API calls require an OAuth bearer token and the xero-tenant-id header for the authorized tenant. Xero also exposes connected tenants through GET https://api.xero.com/connections.

Contact status is preserved as a Xero value, not reduced to a local boolean. ContactStatus is commonly ACTIVE or ARCHIVED. If Xero returns a privacy-related status such as GDPRREQUEST, the app preserves that status in Attio.

Credit note webhooks can be added during scope because credit notes are documented in Xero's webhook surface. Payments and repeating invoices are handled through scheduled or on-demand Accounting API sync rather than webhooks, because they are not documented Xero webhook categories in the validated source packet.

Xero API rate limits and backfill pacing

Xero rate limits shape how backfills run. The validated brief records 5 concurrent calls per tenant, 60 calls per tenant per minute, daily tenant limits of 1,000 calls for Starter and 5,000 calls for Standard and higher tiers, and an app-wide limit of 10,000 calls per minute.

That is why initial imports are queued instead of fired all at once. The app uses pagination, tenant-level throttling, and retry handling for 429 responses so support actions and live sync are not starved by a large historical job.

How reliability is handled

Xero signs webhook payloads with HMAC-SHA256 and sends the signature in x-xero-signature. The managed delivery layer verifies the signature before an event reaches the Attio app, records each delivery attempt, retries transient failures, and supports replay during support.

Xero documents webhook retry behavior when a receiver does not return an acceptable response. It retries for up to 24 hours, can disable the subscription after continued failure, and saves events in Retry or Disabled states for up to 31 days for replay after the subscription is healthy. The Attio app still treats every write as idempotent because Xero does not document exactly-once delivery.

Contact writes use ContactID as the stable Xero identifier. Invoice writes use InvoiceID. Retried deliveries and replayed events update the existing Attio record rather than creating duplicates.

How setup works

Setup separates webhook authenticity from API authorization. Webhook signing proves an event came from Xero. OAuth API access lets the app fetch the current Xero contact or invoice for the authorized tenant.

  1. Scope the Xero and Attio model. We confirm which Xero tenant should feed Attio, whether customers, suppliers, invoices, or credit notes are in scope, how records should match, and which fields belong in Attio.
  2. Connect authorized Xero API access. Webhook signing verifies that events came from Xero. The app still needs authorized API access to fetch the current contact or invoice. During setup, we connect the Xero tenant using the approved Xero connection model for the engagement, then store the tenant ID used for Accounting API requests.
  3. Configure Xero webhooks. In the Xero Developer Portal, the Xero app is configured with the managed webhook delivery URL and webhook signing key. Xero webhooks are app-level, so events for connected organisations are routed by the tenantId included in each event.
  4. Install the Attio app. We install the custom Attio app into your workspace and provision the agreed Attio attributes, invoice surface, workspace settings, support controls, and resync actions.
  5. Run the initial backfill. The setup flow pulls historical contacts and invoices through the Xero Accounting API using paging and rate-aware queues. Backfills can be resumed or repeated when the workspace needs reconciliation.
  6. Turn on live sync. After backfill, Xero webhooks handle new and changed records. Record-level and bulk resync actions remain available in Attio for support, catch-up, and one-off refreshes.

Known limitations

Custom app, not a marketplace install

This is a scoped custom engagement. It is not a public Xero marketplace app, not an Attio Marketplace app, and not a one-click install.

Xero webhooks are app-level

Xero webhook configuration is per app and covers connected Xero organisations for that app. Multi-tenant builds route by the tenantId on each event, not by assuming each webhook subscription belongs to one tenant.

Webhooks are not the historical backfill path

Webhooks handle new and changed records. Historical contacts and invoices are imported through the Xero Accounting API with paging and rate-aware queues.

Payments and repeating invoices are not webhook-first

Credit note webhooks can be added during scope. Payments and repeating invoices are handled through scheduled or on-demand API sync, because they are not documented Xero webhook categories in the validated source packet.

Backfills are rate-limited

Large Xero tenants take time to import. The app respects Xero's 5 concurrent calls per tenant, 60 calls per tenant per minute, daily tenant limits, and app-wide minute limit.

Attio is not a second accounting system

The build maps useful CRM context into Attio. It does not mirror every Xero contact field, invoice field, tax field, attachment, or accounting workflow.

Tax IDs stay in Xero by default

Tax IDs and accounting-only identifiers are excluded from Attio unless there is a clear workflow and explicit scope agreement.

Schema changes need controlled reprovisioning

If the Attio schema changes after launch, we update the field map and reprovision the affected app surfaces before relying on new webhook writes.

Frequently asked questions

Is this a public Xero marketplace app?

No. This is a client-proven Xero-to-Attio custom app pattern, not a public marketplace app or a one-click install. We adapt the scaffolding to the Xero tenant, Attio schema, data minimization rules, and support controls each engagement needs.

What Xero data flows into Attio?

The standard pattern syncs Xero Contacts and Invoices. Customers and suppliers sync into Attio with the fields that matter for CRM use: ContactID, ContactStatus, customer and supplier flags, ContactNumber, and scoped billing-contact indicators. Invoices sync into a dedicated Attio invoice surface with InvoiceID, InvoiceNumber, ContactID, Type, Status, Date, DueDate, SubTotal, TotalTax, and Total. We do not mirror every Xero field.

Which Xero webhook events are used?

The standard webhook set is contact create/update and invoice create/update. Credit note webhooks can be added during scope when the workflow needs them. Payments and repeating invoices are handled through scheduled or on-demand Accounting API sync, not Xero webhooks, because they are not documented webhook categories in the validated source packet.

How does the app know which Xero organisation an event belongs to?

Xero webhooks are configured at the app level and events include tenantId and tenantType. Accounting API calls then use the authorized bearer token plus the xero-tenant-id header, so the app can fetch the current record from the right Xero tenant.

How are Xero contacts matched to Attio records?

The canonical Xero key is ContactID, which Xero documents as the unique contact reference to use instead of contact name. During scope, we decide whether existing Attio People or Companies should also be matched by email, domain, or another workspace-specific identifier before a new record is created.

What happens if webhook delivery fails?

The managed gateway verifies the Xero signature, records each delivery attempt, retries transient failures, and supports replay during support. Xero also documents retry behavior for webhook receivers that do not return an acceptable response, so the Attio app still processes events idempotently by ContactID and InvoiceID.

Why can historical backfills take time?

Xero rate limits are per tenant and app connection. The validated brief records 5 concurrent calls per tenant, 60 calls per tenant per minute, daily limits of 1,000 calls for Starter and 5,000 for Standard and higher tiers, plus an app-wide 10,000 calls per minute limit. Backfills are queued and paced so live sync and support actions do not fight the same limits.

API sources checked

Need this to do something it doesn't?

We started by building Xero Sync for Attio for our own client projects. If you need a version that handles your specific workflow, different fields, different triggers, custom mapping, we can build that. Most custom integrations ship in 2 to 4 weeks.