Salesforce | Xero Integration

This article is prepared by our Salesforce Developer Lizaveta Vasilyeva.

Xero is a cloud-based accounting software for small and medium-sized businesses. This system gives an ability to work with bank feeds, invoicing, accounts payable, expense claims, fixed asset depreciation, purchase orders, bank reconciliations, and standard business and management reporting.

There are two options on how you can try out Xero:
1. Trial organization, where everything can be set up from the scratch.
2. Demo Company with predefined data with almost all Xero features except for automatic bank feeds and the ability to invite other users.

We can establish a connection between Salesforce and Xero to migrate records of different objects, email invoices to the client, etc.

For establishing connections we need to create a connected app. There are 3 types of integrations provided while creation:
  • Web app with (standard auth code).
  • Mobile or desktop app (auth code with PKCE).
  • Connected app (premium one-to-one integration that utilises the client credentials grant type and available only to Xero organizations in UK, Australia and New Zealand).

One of the things that I’ve found is that for one tenant you can have only 2 active web apps.

In the next steps you will see how we can connect Xero and Salesforce using a web app.

Xero Side (app creation)

Open the following link:

Click the New App button.

Specify App Name, Company or Application URL, Redirect URI. In the first step Redirect URI can be any. After Auth Provider creation in Salesforce this value will be replaced with a callback URL.

After the app is created, open Configuration on the left panel and click Generate Secret.

Copy Client Id and Client Secret, save them somewhere, because you will need them in the next step.

Salesforce side

Auth provider creation:

In Setup find Auth Providers and create a new one with the type Open ID Connect. Specify necessary information and save.

Authorize Endpoint URL:

Token Endpoint URL:

Copy Callback URL from created auth provider and replace Redirect URIs in Xero app with it.

Named credentials creation:

In Setup find Named Credentials. Use the drop-down menu next to the New button, choose New Legacy. Specify all parameters like on the screen below:

Scope: offline_access accounting.settings openid profile email accounting.transactions accounting.contacts.


* Before saving the named credential be sure that you logged in to the Xero.

Click Save. After that you will be redirected to a new page where you need to choose the Xero Organization to which your application will be connected. After choosing organization click Allow Access.

After establishing a connection we need to get a tenant-id, which is used for making callouts. To do this, open anonymous apex and run the following code:

HTTPRequest request = new HTTPRequest();
request.setMethod( 'GET' );
request.setEndpoint( 'callout:Xero_Named_Credential/'+'connections' );
request.setHeader( 'Accept', 'application/json' );
request.setHeader( 'xero-tenant-id', '' );
HTTPResponse response = new HTTPResponse();
HTTP objHTTP = new HTTP(); response = objHTTP.send( request );
In the response body you’ll find a tenant-id, which can be stored in a custom setting. This is how tenant-id is used during request to Xero:
request.setHeader('xero-tenant-id', tenantIdValueFromCustomSetting);
In the next part I will tell you about interesting facts and issues which you might face during Salesforce/Xero integration.

One of the things that needs to be remembered is that Xero API like Salesforce has limits:
  • Concurrent Limit: 5 calls in progress at one time
  • Minute Limit: 60 calls per minute
  • Daily Limit: 5000 calls per day
There is also a limit to the number of API calls your app can make per minute across all tenants.
  • App Minute Limit: 10,000 calls per minute
If a limit is reached, you can use the Retry-After header which will show how long to wait before making another call.
During integration we can make GET, POST, PUT, DELETE requests, but not each object supports all of them. For example Invoice object doesn’t include DELETE request, instead to remove record we need to use POST. Depending on the status there will be 2 ways, how it can be implemented:

1) If invoice status is DRAFT or SUBMITTED we set it to DELETED in the request body.
2) If invoice status is AUTHORISED we set it to VOIDED.

Interesting lifehacks that were found during integration with Xero

In fact I haven’t got a chance to work with all of the Xero objects, but here you can find useful information about some of them in order to make your implementation less time consuming. I'll go through each object I've worked with separately.


When we use POST or PUT methods there are 3 fields that are required:
  • Type
  • Contact
  • LineItems collection
Contact is a separate object and there seems to be no need to make a separate call to Contact endpoint to create or update record in Xero. If you include it in the invoice request body, the record will be inserted or updated.


When the contact is created, Name (Full name of contact/organization) field is required. If you use a full name of contact from Salesforce to create a new record in Xero and there are more than one record with the same name, but they represent different people, you’ll face the problem. The problem is that Xero perceives the same value in the name field and different information in other fields as one record. It leads to assignment of invoices, credit notes to incorrect contacts. One of the options which was used to resolve this was to add Salesforce contact id to the name.

Purchase Order

In documentation related to GET request there is a list of fields which are returned. We need to receive all purchase orders during integration and one of many fields, specifically Total Discount should be updated in Salesforce. It turnes out that this field doesn’t appear in the response body. To make it appear, an individual request using PurchaseOrderID or PurchaseOrderNumber should be made.




Payment record can’t be updated using API, in order to do this, existing record should be deleted and new one created instead.

Tax Rates

Be attentive with the organization country. Our team used trial organization for testing purposes, where the country was different from the main organization. After the deployment of the logic responsible for tax rate creation we received the error “The report tax type is required.” It happened because report tax type was required for the AU, NZ and UK organizations and one of these 3 was used in the main organization, but in the trial there was another country.

Also one of the important things is: if you send records from Salesforce to Xero and some of the records fail due to validation error, Xero returns only failed records, showing the error. In case if you need to receive ids of successfully created records to save this information in Salesforce, the GET request should be made.