Connect Zoho Books to DualEntry so customers, items, invoices, and customer payments flow into your DualEntry ledger as AR master data and transactions. The integration is read-only toward Zoho - DualEntry pulls from the Zoho Books API v3 and never posts back to Zoho.
Prerequisites
Confirm the following before connecting:
- Admin access in DualEntry to create integrations and map accounts.
- A Zoho Books user who can authorize OAuth for your organization and the Books organization you want to sync.
- A DualEntry company selected when you create the integration. The integration is bound to one company, but invoice lines can still post to other DualEntry companies when Zoho invoice custom fields match - see Configure invoice custom fields.
- Your chart of accounts configured in DualEntry, including the default income and default expense accounts you’ll map for auto-created items and line items not tied to a mapped Zoho item.
- Zoho Books invoice custom fields set up with the exact labels DualEntry reads:
Location, Service Line, and Company. Without the right labels, location and service-line dimensions don’t populate.
- A classification in DualEntry named
Service Type whose lines match your Zoho Service Line values by name. Location values resolve against a classification named Location (DualEntry creates or updates this classification automatically from Zoho values).
How to connect
Authentication is OAuth 2.0 (authorization code). DualEntry stores your Zoho access token, refresh token, accounts-server URL, and API domain, then refreshes the access token automatically when it expires.
- In DualEntry, navigate to Settings → Integrations → Zoho Books.
- Choose Connect, complete Zoho’s consent flow in the popup, and record the accounts server Zoho returns during OAuth (for example
https://accounts.zoho.com or a regional variant). Zoho includes this value as the accounts-server parameter on the redirect.
- Provide the authorization code, the accounts server, and the DualEntry company you want to bind the integration to. DualEntry exchanges the code for tokens, loads the Zoho Books organization list, sets the integration name from the first organization returned, marks the integration Connected, and queues an initial sync.
- Map the default Expense account and Income account integration rows to your DualEntry GL accounts. The pre-setup phase creates these placeholder rows; until both are mapped, the integration isn’t fully set up.
- Wait for the first sync to complete. Setup completion depends on a successful initial sync (the integration tracks last-successful-sync internally).
To run another pull without waiting for the scheduler, choose Sync now in the integration settings. Sync-now is blocked while the integration is paused.
What syncs
Each sync run pulls Zoho Books data in this order: invoices → payments → customers → items → locations. Each phase uses its own budget for Zoho “detail” API calls so a single sync can’t exhaust your daily quota.
| Zoho Books data | DualEntry record | Notes |
|---|
| Contacts | customer | Bulk list uses last_modified_time since integration creation. A detail fetch may run once per contact to fill country from the billing address. |
| Items | item | Catalog items. New DualEntry items default to service type and use your mapped default expense and income accounts. |
Invoice custom field values labeled Location | classification line under Location | DualEntry ensures a Location classification exists and maps values to lines automatically. |
Invoice custom field values labeled Service Line | item rows | Used when an invoice is driven by a service line instead of catalog line items. |
| Invoices | invoice | Draft invoices are skipped at detail fetch. Non-draft invoices require the Zoho customer to be mapped first. PDF attachment from Zoho’s template PDF endpoint is best-effort - failures are logged and don’t block the invoice. |
| Customer payments | customer_payment | Requires payment mode on the list payload and a detail fetch for applied invoices - see Map payment-mode accounts. |
Customer, invoice, and customer-payment lists use Zoho’s last_modified_time ordering and stop paging when the oldest row on a page is older than the integration’s creation date. Items and locations are fetched in full (paginated) when their sync steps run.
Very large backfills may need multiple sync runs across multiple days because of Zoho’s daily API quota. The integration enforces a per-sync cap on detail fetches so one run cannot exhaust the quota; if the cap is hit, the sync ends with a rate-limit error and the next scheduled run picks up where it left off.
DualEntry reads Zoho Books invoice custom fields by label. Three labels are recognized today:
Company - value must match a DualEntry company name. When it matches, the invoice posts to that company; otherwise the integration’s default company is used.
Location - value must match a classification line name under your DualEntry classification named Location. The classification name and the Zoho field label both have to align.
Service Line - when present on an invoice, line construction follows the service-line path. Service-line values are matched to classification lines under a DualEntry classification named Service Type.
Custom field labels and DualEntry classification line names must match exactly - including punctuation and case. If your Zoho labels read “Office” instead of “Location”, or your DualEntry classification is named “Department” instead of “Service Type”, DualEntry doesn’t attach those dimensions to the journal entry. This is the most common source of “my dimensions aren’t showing up” support tickets.
If your Zoho organization uses different label conventions, rename the Zoho custom fields rather than expecting DualEntry to match alternate labels. The integration matches on literal string equality with no fallbacks.
Map payment-mode accounts
Each customer payment needs a DualEntry GL account for the Zoho payment mode (Stripe, Check, Cash, Bank Remittance, Bank Transfer, Credit Card, ACH/WIRE). DualEntry creates a placeholder integration record for each (mode, company) pair the first time it sees a payment in that mode, and you map it to the correct cash or undeposited-funds account.
To complete payment-mode mapping:
- Open the integration’s mapping screen after the first sync of customer payments.
- For each payment-mode row, choose the DualEntry GL account where payments in that mode should post.
- Resync to apply the mapping to any payments that were captured pending the mapping.
Payments allocate to invoices that are already imported and mapped. If no applied invoices are importable, the payment fails with a “no importable invoices” error. Some Zoho-side errors are treated as permanent - for example, payment amounts that exceed the invoiced balance or fall in a locked period - so DualEntry marks the payment skipped instead of retrying forever.
Troubleshoot sync errors
When a record fails to sync, the most common causes:
| Symptom | Likely cause | Resolution |
|---|
OAuth or 424 on create | Invalid code, clock skew, wrong accounts_server, or a Zoho app misconfiguration. | Retry with a fresh authorization code; verify Zoho client ID/secret/redirect on the server; confirm Books organization access. |
| Invoice never imports | The invoice is in Draft status in Zoho. | Finalize or send the invoice in Zoho, then resync. |
| Invoice error: customer missing | The Zoho customer isn’t yet mapped to a DualEntry customer. | Let the customer sync run, map any unmapped customers, and resync. |
| Payment fails: account not found | The payment-mode row isn’t mapped to a GL account. | Map the placeholder payment_account record for that company and mode. |
| Payment skipped permanently | Zoho returned a business-rule error (over-application, locked period, and similar). | Fix the data in Zoho or accept the skip - DualEntry doesn’t keep failing the same record. |
| Location or service line missing on JE | Custom field labels or DualEntry classification line names don’t match. | Align Zoho custom field labels (Location, Service Line, Company) and DualEntry classification line names (Location, Service Type). |
| PDF missing on invoice | Detail budget exhausted, Zoho PDF error, or transient network issue. | The invoice still posts; review logs or rerun sync. |
Stale token / random 401 | The access token expired. | The next API call refreshes the token automatically; if the refresh fails, reconnect via OAuth. |
For Zoho-wide outages, see the Zoho status page.
FAQ
Does DualEntry write back to Zoho Books?
No. The connector only pulls - Zoho remains the system of record for AR operations.
Which Zoho product is supported?
Zoho Books (API paths under /books/v3/). Zoho Invoice and other Zoho suites aren’t supported unless they expose the same Books endpoints your deployment targets.
Why does setup require income and expense accounts?
New item records created from Zoho lines fall back to the default income and expense accounts when Zoho doesn’t supply a mapped catalog item. Without those defaults, line creation fails.
Can I use a different classification name than “Service Type”?
No. The integration matches Service Line custom field values to lines under a DualEntry classification literally named Service Type. Renaming the classification breaks the mapping.
For maintainers
The details below describe deployment and connector configuration, not user-facing features.
- OAuth app config (server-side environment):
ZOHO_CLIENT_ID, ZOHO_CLIENT_SECRET, ZOHO_REDIRECT_URI. The redirect URL registered in the Zoho API console must match the deployment’s redirect URI.
- Internal create endpoint:
POST /api/integrations/zoho/ accepts authorization_code, accounts_server (URL-encoded when required by your client), company_id, and an optional integration_id for updates.
- Internal trigger-sync endpoint:
POST /api/integrations/zoho/{integration_id}/trigger-sync/ (blocked when the integration is paused).
- Internal field plumbing: the integration sets
remote_id and name from the first Zoho organization returned, queues sync_integration on connect, and tracks last_sync_at for setup-completion flags.
- Incremental sync details: customer/invoice/payment lists page using
last_modified_time and stop when the oldest row is older than the integration’s created_at, with a five-day delta window on invoice and payment upserts to allow for late-arriving Zoho updates.
- API quota: Zoho publishes a 10,000 requests per day limit for typical Books API usage. The Zoho SDK respects
429 responses with Retry-After-style backoff and retries a few times before surfacing the failure.
Result
After OAuth, default-account mapping, payment-mode mapping, and the first successful sync, Zoho Books AR activity and master data land in DualEntry with optional company, location, and service-type dimensions driven by Zoho invoice custom fields, plus PDF attachments on invoices when Zoho and API limits allow. To audit the resulting entries, see Customer payments and Journal Entries. To compare other OAuth-based integrations, see Stripe. To connect additional systems, return to Integrations.