Zeng Book
ProductIndustriesIntegrationsPricingResources
Sign inContact salesTry it free
Introduction
  • Overview
  • Quickstart
API reference
  • Authentication
  • Errors
  • Rate limits
  • Pagination
  • Interactive explorer
Resources
  • Organization
  • Leads
  • Clients
  • Projects
  • Quotations
  • Invoices
Integrations
  • Overview
  • Webhooks
  • Public portal
  • Zapier
  • Make / n8n recipes
  • Xero (coming soon)

Integrations

Xero accounting

Connect Xero, then every invoice you mark paid in Zeng Book pushes to Xero as an AR invoice — automatically. Retention is preserved as a negative line item; GST is snapshotted at issue.

Status: invoice sync live, contact sync coming
OAuth connect, automatic invoice push, and manual retry are all live. Sync of clients → Xero contacts and chart-of-account mapping are scheduled next.

How it works

  1. Connect your Xero organisation from Settings → Integrations. (Owner / admin only.)
  2. Set your default revenue account code on the same page. Xero Singapore's default Sales account is 200 — use that unless you want a different mapping.
  3. Issue invoices as usual. Nothing happens on Xero's side until an invoice is marked PAID.
  4. When you mark an invoice paid (manually or via Stripe), Zeng Book pushes it to Xero as an AUTHORISED invoice. The push runs fire-and-forget— your "mark paid" click returns immediately; the push happens in the background and writes its outcome to the invoice's Xero sync card.
  5. If the push fails (Xero rate-limit, expired token, wrong account code, etc.), the invoice detail page shows a red error card with a Retry push button.

What gets pushed

Zeng Book maps your invoice into a Xero ACCREC (accounts-receivable) invoice:

json
{
  "Type": "ACCREC",
  "Contact": { "Name": "Tan Renovations Pte Ltd", "EmailAddress": "[email protected]" },
  "Date": "2026-05-15",
  "DueDate": "2026-06-14",
  "InvoiceNumber": "INV-2026-0117",
  "Reference": "Zeng Book INV-2026-0117",
  "Status": "AUTHORISED",
  "LineAmountTypes": "Exclusive",
  "CurrencyCode": "SGD",
  "LineItems": [
    {
      "Description": "Phase 2 — Carpentry installation (40%)",
      "Quantity": 1,
      "UnitAmount": 18000.00,
      "AccountCode": "200",
      "TaxType": "OUTPUT"
    },
    {
      "Description": "Less retention (5.0%)",
      "Quantity": 1,
      "UnitAmount": -900.00,
      "AccountCode": "200",
      "TaxType": "OUTPUT"
    }
  ]
}
  • Money— Zeng Book stores integer cents; the push divides by 100 for Xero's decimal-dollar format.
  • Retention— a single negative line item at the same TaxType as the rest of the invoice. Math is identical to Zeng Book's subtotal → retention → net → GST → total order.
  • TaxType — OUTPUT when the invoice has a non-zero GST rate, NONE otherwise.
  • InvoiceNumber — copied verbatim from Zeng Book so both systems share the same identifier.
  • Status — pushed as AUTHORISED. Payment reconciliation in Xero is still manual today; automatic payment push is on the roadmap.

Idempotency & retries

The push is idempotent per Zeng Book invoice: each invoice maps to at most one Xero invoice. The mapping is tracked in the XeroInvoiceMapping table with three states:

  • synced — push succeeded. A retry returns the existing Xero invoice ID with no duplicate create.
  • failed — last attempt failed. The retry button deletes the failed row and tries again.
  • (no row) — invoice has never been pushed. First push creates a fresh mapping.

The fire-and-forget push has a 15-second timeout and a try/catch around every external call — a Xero outage never blocks payment recording.

Scopes we request

text
offline_access
accounting.transactions
accounting.contacts
accounting.settings

What's in development

Contact sync

Zeng Book clients sync to Xero contacts on first invoice push. Deduplication by UEN when present, otherwise by name + email. Today contacts are auto-created by name on the invoice POST — full sync gives you control over de-duplication.

Chart-of-accounts mapping

Per-org UI to map Zeng Book sections (carpentry, electrical, etc.) to specific Xero revenue account codes. Today every line uses the single org-level default code.

Payment reconciliation

Automatically log a Payment against the pushed Xero invoice so it shows as PAID in Xero immediately, with the correct bank account code. Today the invoice lands as AUTHORISED and your accountant reconciles payment manually.

For developers

The implementation lives in lib/xero.ts:

  • pushInvoiceToXero(orgId, invoiceId) — idempotent, returns XeroPushResult (never throws)
  • mapInvoiceToXero(invoice, currency, accountCode) — pure function, easy to unit-test
  • getValidAccessToken(orgId) — refreshes the token if it's within 60s of expiry

The hook into the user flow is in lib/invoice-service.ts on the line where recordPaymentEntry flips status to PAID. Same hook in bulkMarkPaid.

To build a custom push pipeline, call pushInvoiceToXero directly from your own action / cron / webhook handler. Or pull invoices via the REST API and push them through your own pipeline.