QuickBooks Online is the dominant accounting platform in the US, Canada, and parts of the EU market. The QuickBooks REST API (Intuit API v3) supports customer creation, invoice generation, payment status retrieval, and webhook notifications. The Kommo integration closes the standard pattern: deal Won -> Customer in QuickBooks -> Invoice -> after payment the CRM is updated, with no manual data entry.
QuickBooks vs Zoho Books vs Wave: When to Choose QuickBooks
| Parameter | QuickBooks Online | Zoho Books | Wave |
|---|---|---|---|
| API | REST v3 | REST | GraphQL |
| Dominant market | US, Canada | Global | US, Canada |
| Price (Simple Start) | $35/month | Free up to $50k | Free (basic) |
| Multi-currency | Plus and above | All plans | Partial |
| Built-in payment processing | Yes (QuickBooks Payments) | Yes | US/Canada |
| Ecosystem | 750+ integrations | Zoho 50+ products | Independent |
QuickBooks is chosen by companies working with US/CA clients or auditors — it is the de facto standard in North America. Comparisons with Zoho Books and Wave are covered in separate articles.
What Gets Synchronised
Kommo -> QuickBooks: — Deal contact -> Customer in QB (deduplication by email via query API) — Deal name and amount -> Invoice line items — Payment terms from custom field -> Terms (Net 15, Net 30, etc.) — Invoice ID and link -> custom fields in Kommo card
QuickBooks -> Kommo:
— Webhook Payment with txnStatus = Completed -> “Paid” field in deal
— Move deal to “Payment Received” stage
— Payment date and amount -> Note on deal
Architecture
Kommo Webhook: deal Won
↓ Backend
1. GET /api/v4/leads/{id} + contacts
-> name, email, amount, payment terms
2. QB API: POST /v3/company/{realmId}/query
-> SELECT * FROM Customer WHERE PrimaryEmailAddr = '{email}'
-> found: use Id
-> not found: POST /v3/company/{realmId}/customer
3. QB API: POST /v3/company/{realmId}/invoice
-> CustomerRef + Line items + DueDate
-> get Id, DocNumber, InvoiceLink
4. QB API: POST /v3/company/{realmId}/invoice/{id}/send
-> send invoice to client by email
5. Kommo: PATCH /leads/{id}
-> update fields qb_invoice_id, invoice_url
QuickBooks Webhook: Payment (txnStatus = Completed)
↓ Backend
1. GET /v3/company/{realmId}/payment/{paymentId}
-> find LinkedTxn with Invoice Id
2. GET from storage: find kommo_deal_id by qb_invoice_id
3. Kommo: PATCH /leads/{deal_id}
-> stage -> "Paid", field payment_date
QuickBooks REST API: Key Requests
QuickBooks API uses OAuth 2.0 (Authorization Code Flow). All requests go to https://quickbooks.api.intuit.com/v3/company/{realmId}/. Required parameter: ?minorversion=75.
Find or create Customer:
import requests
QB_BASE = f'https://quickbooks.api.intuit.com/v3/company/{REALM_ID}'
HEADERS = {
'Authorization': f'Bearer {access_token}',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
PARAMS = {'minorversion': '75'}
def find_or_create_customer(email: str, display_name: str, company: str) -> str:
# Search by email
query = f"SELECT * FROM Customer WHERE PrimaryEmailAddr = '{email}'"
resp = requests.post(
f'{QB_BASE}/query',
params={**PARAMS, 'query': query},
headers=HEADERS
)
customers = resp.json()['QueryResponse'].get('Customer', [])
if customers:
return customers[0]['Id']
# Create new customer
payload = {
'DisplayName': display_name,
'CompanyName': company,
'PrimaryEmailAddr': {'Address': email}
}
resp = requests.post(
f'{QB_BASE}/customer',
params=PARAMS,
json=payload,
headers=HEADERS
)
return resp.json()['Customer']['Id']
Create invoice:
from datetime import date, timedelta
def create_invoice(customer_id: str, deal_name: str, amount: float,
due_days: int = 30) -> dict:
today = date.today().isoformat()
due_date = (date.today() + timedelta(days=due_days)).isoformat()
payload = {
'CustomerRef': {'value': customer_id},
'DueDate': due_date,
'TxnDate': today,
'Line': [
{
'Amount': amount,
'DetailType': 'SalesItemLineDetail',
'SalesItemLineDetail': {
'ItemRef': {'value': '1', 'name': 'Services'}, # item from QB
'Qty': 1,
'UnitPrice': amount
},
'Description': deal_name
}
]
}
resp = requests.post(
f'{QB_BASE}/invoice',
params=PARAMS,
json=payload,
headers=HEADERS
)
invoice = resp.json()['Invoice']
return {'id': invoice['Id'], 'doc_number': invoice['DocNumber']}
Send invoice to client:
def send_invoice(invoice_id: str, client_email: str):
requests.post(
f'{QB_BASE}/invoice/{invoice_id}/send',
params={**PARAMS, 'sendTo': client_email},
headers=HEADERS
)
# QuickBooks sends a standard email with a payment link
Refreshing OAuth Tokens
The QuickBooks access token expires after 60 minutes. The refresh token expires after 100 days. It is important to implement auto-refresh:
def refresh_access_token(refresh_token: str) -> dict:
import base64
credentials = base64.b64encode(
f'{CLIENT_ID}:{CLIENT_SECRET}'.encode()
).decode()
resp = requests.post(
'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer',
headers={
'Authorization': f'Basic {credentials}',
'Content-Type': 'application/x-www-form-urlencoded'
},
data={
'grant_type': 'refresh_token',
'refresh_token': refresh_token
}
)
return resp.json() # access_token + refresh_token (rotating)
QuickBooks uses rotating refresh tokens — each refresh issues a new refresh token and invalidates the old one. This must be stored in the database, not in a config file.
Payment Webhook
QuickBooks webhooks are configured in the Intuit Developer Portal. The payload is minimal: only entity type, ID, and operation. Full details must be fetched separately:
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhooks/quickbooks', methods=['POST'])
def qb_webhook():
payload = request.json
for notification in payload.get('eventNotifications', []):
realm_id = notification['realmId']
for entity in notification.get('dataChangeEvent', {}).get('entities', []):
if entity['name'] == 'Payment' and entity['operation'] == 'Create':
payment_id = entity['id']
# Request payment details
payment = get_payment_details(realm_id, payment_id)
# Update Kommo by invoice_id from payment
sync_payment_to_kommo(payment)
return '', 200
Real-World Case
Consulting company (US market, 20–30 projects per quarter, clients in the US and Canada):
- Before: after Won, the manager switched to QuickBooks, manually created the customer, generated the invoice, and sent the email. Average time from Won to invoice: 3–4 days.
- After: Won in Kommo -> client receives QuickBooks invoice within 5 minutes -> payment status automatically updates in Kommo upon receipt of payment.
- Additionally: the accountant stopped requesting data from managers for invoicing — everything comes from the CRM automatically.
A similar pattern using Zoho Books — there the REST API uses regional OAuth but without rotating refresh tokens. For the US market, QuickBooks is the standard choice.
Who This Is Relevant For
- Clients primarily in the US and Canada — QuickBooks is the de facto standard
- 10+ invoices per month currently created manually
- Cycle: Won -> invoice -> payment -> next pipeline stage
- Accountant and manager work in different systems and need synchronisation
- QuickBooks Payments is used for online payments
Frequently Asked Questions
How complex is QuickBooks OAuth to maintain?
The main complexity is rotating refresh tokens: each time a token is refreshed, the new refresh token must be saved. If this is missed — the next refresh will use the old invalidated token and authorisation will break. In practice: store tokens in a database with a timestamp, refresh 5 minutes before the access token expires, and log every refresh.
Which Item should I use when creating an invoice?
An Item (product/service) in QuickBooks is a required object in a Line. You can create one universal Item “Consulting Services” via the QB UI and use its ID for all invoices from Kommo. Or create Items dynamically via POST /item — but using a fixed Item for the integration is simpler.
Are there QuickBooks API rate limits?
Yes. 500 requests per minute for a real OAuth app. For a typical Kommo volume (up to 100 deals per month), limits are irrelevant. The webhook payload is minimal — each notification requires an additional GET request, which should be considered at high volume.
How do I test with the QuickBooks Sandbox?
Intuit provides a sandbox environment: sandbox-quickbooks.api.intuit.com. A sandbox company is created automatically when registering in the Intuit Developer Portal. Webhook testing — via ngrok or similar tools (Intuit cannot send webhooks to localhost).
Is QuickBooks Plus required for the API?
The API is available on all plans, including Simple Start ($35/month). Multi-currency requires Plus and above. For the US market without multi-currency, Simple Start is sufficient.
Summary
- QuickBooks Online REST API v3: OAuth 2.0 with rotating refresh tokens,
realmIdin every request - Search Customer via query API, create invoice with Line items, send via
/sendendpoint - Webhook on Payment -> automatic stage update in Kommo
- Rotating refresh tokens — the key architectural consideration, requires storage in the database
- Typical development time: 2–3 weeks
If you work with QuickBooks and Kommo and want to automate invoice generation — describe your pricing structure and payment workflow. Exceltic.dev will analyse the mapping and propose an architecture.