Integrating Kommo with Xero lets you automatically create an invoice in your accounting system the moment a manager moves a deal to “Won” status - with no tab-switching, no manual data copying. Xero API v2 supports full bidirectional sync: contact creation, invoice issuance, and payment tracking.
This is a typical task for custom Kommo integrations: the native Xero widget in the Kommo marketplace exists, but covers only the basic scenario and doesn’t support custom fields, tax codes, or multi-currency.
Why the Native Xero Widget Breaks in Production
Xero has an official widget in the Kommo marketplace. In practice it breaks for several reasons.
Problem 1 - field mapping. The widget only reads standard deal fields: amount and name. Custom fields (tax region, discount, PO number) are ignored. The result: accounting receives an invoice with incorrect data and fixes it manually.
Problem 2 - contact duplication. The widget creates a new Contact in Xero for every deal. If the company already exists in Xero under a slightly different name, a duplicate appears. After three months of use, accounting sees hundreds of duplicates.
Problem 3 - tax codes. Xero requires an explicit TaxType for each invoice line. The widget applies the default code, which doesn’t match the actual rate for a given client or country.
Problem 4 - multi-currency. If you have clients in EUR and GBP, the widget can’t pass the currency from the deal field - invoices are always created in the default currency.
A custom integration via Xero API v2 solves all four problems.
Integration Architecture
Stack: Python 3.11, Kommo Webhook, Xero API v2 (OAuth 2.0 with PKCE), PostgreSQL for ContactID mapping storage.
How it works:
- Kommo sends a webhook when a deal status changes to “Won”.
- The microservice receives the event and extracts deal and contact fields.
- Search or create a Contact in Xero with duplicate checking by email.
- Create an Invoice with correct line items, TaxType, and currency.
- Write the XeroInvoiceID back to a custom field in the Kommo deal.
- When a payment webhook arrives from Xero - update the deal status.
Xero authentication: OAuth 2.0 with refresh token. Xero revokes the access token every 30 minutes. The refresh token lives 60 days with active use. Tokens are stored encrypted in PostgreSQL and refreshed automatically.
import requests
from datetime import datetime, timezone
import json
XERO_TOKEN_URL = "https://identity.xero.com/connect/token"
XERO_API_BASE = "https://api.xero.com/api.xro/2.0"
def refresh_xero_token(refresh_token: str, client_id: str, client_secret: str) -> dict:
resp = requests.post(XERO_TOKEN_URL, data={
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": client_id,
"client_secret": client_secret,
})
resp.raise_for_status()
return resp.json()
def find_or_create_contact(tenant_id: str, access_token: str,
name: str, email: str) -> str:
headers = {
"Authorization": f"Bearer {access_token}",
"Xero-Tenant-Id": tenant_id,
"Accept": "application/json",
}
# Search by email
search = requests.get(
f"{XERO_API_BASE}/Contacts",
headers=headers,
params={"where": f'EmailAddress="{email}"'}
)
contacts = search.json().get("Contacts", [])
if contacts:
return contacts[0]["ContactID"]
# Create new contact
payload = {"Name": name, "EmailAddress": email}
create = requests.post(
f"{XERO_API_BASE}/Contacts",
headers={**headers, "Content-Type": "application/json"},
json={"Contacts": [payload]}
)
create.raise_for_status()
return create.json()["Contacts"][0]["ContactID"]
def create_invoice(tenant_id: str, access_token: str,
contact_id: str, deal: dict) -> str:
headers = {
"Authorization": f"Bearer {access_token}",
"Xero-Tenant-Id": tenant_id,
"Content-Type": "application/json",
"Accept": "application/json",
}
invoice = {
"Type": "ACCREC",
"Contact": {"ContactID": contact_id},
"Date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
"DueDate": deal.get("due_date", ""),
"CurrencyCode": deal.get("currency", "USD"),
"Reference": deal.get("custom_po_number", ""),
"LineItems": [{
"Description": deal["name"],
"Quantity": 1.0,
"UnitAmount": float(deal["price"]),
"TaxType": deal.get("tax_type", "NONE"),
"AccountCode": deal.get("account_code", "200"),
}],
"Status": "AUTHORISED",
}
resp = requests.post(
f"{XERO_API_BASE}/Invoices",
headers=headers,
json={"Invoices": [invoice]}
)
resp.raise_for_status()
return resp.json()["Invoices"][0]["InvoiceID"]
Step-by-Step Implementation
Step 1 - register Xero app. Go to developer.xero.com, create a “Web app” application. Obtain Client ID and Client Secret. Set the Redirect URI of your microservice.
Step 2 - OAuth flow. The initial authorization is performed once via browser. The user grants access to the Xero organisation. You receive access + refresh tokens and store them in the database.
Step 3 - Kommo webhook. In Kommo settings, create a webhook for the “Deal: stage change” event. URL - your microservice. Filter: only transitions to “Won” status.
Step 4 - field mapping. Describe in a config file which Kommo custom field maps to which Xero parameter:
{
"tax_type_field_id": "12345",
"currency_field_id": "67890",
"po_number_field_id": "11111",
"xero_invoice_id_field": "22222"
}
Step 5 - reverse sync. Configure a Xero webhook for INVOICE events with status PAID. On receipt - find the deal by InvoiceID and update a tag or custom field in Kommo.
Step 6 - monitoring. All invoice creation errors are logged and sent to the accounting team’s Slack channel. The deal is not blocked - the error is recorded as a note on the Kommo deal.
Real Case: SaaS Company in the Netherlands
B2B SaaS company with a 22-person team, 40-60 new deals per month. Clients in 12 countries with different tax rates (NL VAT 21%, DE VAT 19%, UK VAT 20%, US no VAT).
Before integration: the finance manager spent 3-4 hours per week manually creating invoices in Xero from Kommo. TaxType errors appeared in 15% of invoices - discovered during quarterly reconciliation.
After integration: an invoice is created automatically within 30 seconds of closing a deal. The correct tax code is pulled from the “Client Country” custom field via a mapping table. Over 6 months of operation - 0 TaxType errors.
Numbers: 3.5 hrs/week -> 0, saving ~180 hours per year. The project cost paid back in 8 weeks.
Who It’s For
Kommo + Xero integration is relevant for companies that:
- Have more than 20 new deals per month and use Xero for accounting
- Have clients in multiple countries with different tax rates
- Use custom deal fields (PO number, project, department)
- Require bidirectional sync: payment status from Xero back to CRM
Xero is popular in the UK, Australia, New Zealand, and a growing number of EU companies. If your accounting runs on Xero - configuring the Kommo pipeline with an automatic “Won” trigger closes the full cycle from lead to paid invoice.
Frequently Asked Questions
Does the integration work with Xero multi-currency?
Yes. The Xero API accepts a CurrencyCode field on each invoice. In our integration the currency is taken from a custom Kommo deal field or from the “Amount” field with currency selection. For multi-currency you need to ensure Multiple Currencies is enabled in Xero (available on Established and above plans). Exchange rate conversion at the invoice date happens automatically on Xero’s side.
What happens if Xero is unavailable when a deal is closed?
The microservice uses exponential backoff: first retry after 30 seconds, then after 2 minutes, then after 10 minutes. If the invoice is not created after 5 attempts, a note with the error is added to the Kommo deal card and a Slack notification is sent. The deal remains in “Won” status - the process is not blocked.
How do you prevent contact duplication in Xero?
Contact search is performed by the email address from the Kommo card. If a contact is found - the invoice is created for them. If not found - a new contact is created with a company name uniqueness check. The ContactID is saved in a custom Kommo contact field - for subsequent deals from the same client the search uses this field first.
Can the deal status be updated automatically on payment?
Yes. Xero sends a webhook when an invoice transitions to PAID status. The service receives the event, finds the deal by the stored XeroInvoiceID, and can add a “Paid” tag or move the deal to a final stage. The specific logic is configured to match your process.
Is creating recurring invoices for subscriptions supported?
Xero supports RepeatingInvoices via API. For SaaS with monthly or annual subscriptions you can configure creating not a one-off invoice but a template with a schedule. However for subscriptions we usually recommend integrating with Chargebee or Recurly - they handle billing cycles more flexibly.
Next Step
If you work in Kommo and your accounting runs on Xero - describe the task to the Exceltic.dev team. We’ll review your field structure, tax logic, and propose an integration architecture. Typically such a project takes 2-3 weeks and requires no changes to existing accounting processes.