We Thought We Were Shipping an Integration. Tally Shipped Us a Philosophy Degree, a Desktop Embassy, and a Mild XML Trauma
Gaurav Singhal
View LinkedInAmbill · Engineering story · Tally Sync
The setup: two worlds that refuse to share a zip code
If you build modern web software for long enough, you start to believe—quietly, dangerously—that the universe is mostly HTTPS, JSON, and a database you can reach from a region.
Then you meet Tally.
Tally is not “another integration checkbox.” For a huge number of finance teams in India, Tally is where the month ends. It is where GST logic meets ledger discipline. It is where auditors ask questions, and where the answer is expected to be in the books, not “in a spreadsheet we emailed once.”
Ambill, meanwhile, is built for the speed of billing operations: invoices, collections, workflows, the messy human part of revenue. That belongs in the cloud because that is where teams collaborate.
So the product problem sounds simple: keep Ambill fast, keep Tally authoritative, and stop humans from copy-pasting reality between two places.
The engineering problem is not simple. It is the kind of problem that politely explains, “Your cloud cannot knock on Tally’s door,” and then watches you learn what that sentence actually costs.
This is the story of how we built Tally Sync—including the parts we do not usually put in a glossy landing page: the confusion, the reverse engineering, the architecture we had never built before, and the moments where we wondered if we had accidentally become XML artisans.

Act I: “Surely there is an API” (spoiler: not the kind we meant)
When an engineer hears “integration,” their brain runs a script:
- Find authentication.
- Find endpoints.
- Send JSON.
- Receive JSON.
- Write tests.
- Sleep.
Tally integration breaks that script at step two—not because Tally is “bad,” but because Tally is not a SaaS API waiting on the public internet. In the common deployment pattern, Tally runs locally: on an office machine, on a workstation, or on a server the customer owns—often the same machine where accounting work happens, sometimes behind policies that treat the outside world with justified suspicion.
That single constraint is not a small footnote. It is an architectural fork in the road.
If Ambill lives in the cloud, and Tally lives beside a human (or beside a Windows service habit), then the cloud cannot “just call Tally.” There is no stable universal hostname for “the customer’s Tally.” There is no neat OAuth dance that replaces physical proximity.
Most of our customers host Tally on their own infrastructure. That is rational: accounting data is sensitive, availability expectations are local, and IT teams have existing practices. But it also means your integration inherits the real world: VPNs, firewalls, remote desktop rituals, “the machine restarted,” “Tally is open but the wrong company is loaded,” and the timeless classic: “It worked yesterday.”
So we stopped asking the naive question: How do we call Tally from our API?
We started asking the correct question: Who is physically allowed to speak to Tally, and how do we make that agent trustworthy, observable, and upgradeable?
The answer became the connector: a small local application that sits next to Tally like a polite interpreter at a diplomatic meeting. It does not try to replace Tally. It tries to be the reliable courier between Ambill’s cloud brain and Tally’s local ear.
If you want one sentence that summarizes the emotional journey of this act: we thought we were integrating software; we discovered we were integrating geography.
Act II: XML — or, how we learned that “documentation” is sometimes a wish
Here is a confession that still feels oddly vulnerable: when we started, we did not really know XML.
Not “we were rusty.” Not “we preferred JSON.” We were web developers. Our world was browsers, APIs, objects, arrays, and frameworks that hide serialization until you do not want it to.
Tally’s integration surface, however, is largely XML in, XML out. That is not a debate. That is the table stakes.
And then came the second problem—worse than not knowing XML syntax. There is no single, dependable, end-to-end manual that tells you exactly how to craft working XML for every real-world object and voucher shape in the way modern platforms ship OpenAPI specs.
Official pages exist. Examples exist. But in practice, examples can behave like Schrödinger’s documentation: some work, some do not, and many fail in ways that do not teach you why—only that you are still wrong.
If you have only lived in ecosystems where “broken example” is rare because CI validates snippets, this is disorienting. You are not failing because you cannot read. You are failing because the ground truth is not fully written down.
So we did what engineers do when the map is incomplete: we went looking for the territory.
The Rosetta export: how we actually discovered what Tally “means”
The turning point was not a tutorial. It was export.
We began exporting every object uniquely from Tally as XML—customers, items, companies, chart-of-accounts relationships, voucher types—anything we needed to mirror or generate. Then we rendered those exports and read them the way you read a core dump when you have no other choice.
These were not cute little snippets. These were enormous XML documents—five thousand lines, ten thousand lines, sometimes more—dense with nesting, repetition, attributes, and internal identifiers that only make sense once you have stared long enough to develop a personal relationship with them.
If you have never done this, imagine trying to learn a spoken language by being handed a dictionary where half the pages are missing, and the other half occasionally contradict each other, and also the dictionary is printed as one continuous scroll.
That is not bitterness. That is respect. Tally is a mature system with decades of real-world accounting complexity compressed into schemas that were never meant to be a beginner-friendly SDK.
We studied those exports until patterns emerged:
- Which tags appear consistently?
- Which fields are optional until they are suddenly mandatory because GST context changed?
- Which identifiers come back stable enough to build mapping on?
- Which “helpful” shapes in exports should not be blindly copied into “create” requests because exports include computed noise?
And then—slowly, stubbornly—we did the unglamorous work: we wrote our own XML by hand, tag by tag, shaping requests until Tally accepted them reliably.
If you want a single phrase that captures the craft: we reverse engineered the language by reading Tally’s own letters back to itself until it agreed with us.
There is humor in hindsight. At the time, it felt less like humor and more like XML boot camp where the drill instructor is your own conscience.
The hand-built era: tag by tag, attribute by attribute
After the exports taught us what Tally returns, we still had to learn what Tally accepts. Those are related dialects, but they are not identical. An export can include computed fields, read-only shapes, or structures that are informative for humans and misleading for naive automation.
So we entered a phase that sounds absurd when you say it out loud: we constructed XML manually, carefully, the way you might assemble legal paperwork—tag by tag, checking each nesting decision, comparing against the giant reference scroll, and testing in small increments so we knew which change caused which outcome.
This was slow. It was also clarifying. When you build by hand, you develop intuition for what is “structural scaffolding” versus what is “business payload.” You learn which omissions Tally tolerates early and which omissions it punishes immediately. You learn which identifiers must match across sections of the document like a chorus that must sing the same note.
If you are a startup reading this and wincing: yes, this is expensive engineering. But it is also how you earn the right to automate safely. Automation without ground-truth discipline becomes a machine that manufactures mistakes at scale.
We eventually replaced much of the hand-building with generators—but those generators were trained by the hand-building, not the other way around. In that sense, the painful manual phase was not wasted time. It was calibration.
Side quest: the tooling around XML is a personality test
Once you live in XML, you quickly develop opinions about parsers, pretty printers, diff tools, and the exact moment a “collapsed subtree” view in an editor becomes an act of self-sabotage.
You learn that “readable XML” is sometimes a lie, because readability for humans is not the same as correctness for Tally. You learn that a beautiful indent can still hide a wrong nesting level—like a tidy desk with one wrong drawer that contains chaos.
You also learn collaboration habits: when you share a payload with a teammate, you include the Tally version context, the company configuration hints you know, and the minimal reproduction path. Otherwise you are not debugging; you are storytelling.
Why this matters beyond our bruised egos
This discovery process changed how we designed Ambill’s side of the integration:
- We stopped treating XML as “a string we concatenate.” We treated it as a contract with strict semantics.
- We built internal guardrails: validation, logging, reproducible payloads, and the discipline to compare “what Tally returned” vs “what we thought we sent.”
- We learned to be suspicious of “clever” generic templating early. Accounting payloads punish cleverness.
If you are attempting a similar integration, budget for this reality: your first milestone is not ‘post an invoice.’ Your first milestone is ‘understand the shape of truth Tally expects.’ Everything else is downstream.

Act III: Tally’s internal architecture — everything is a ledger, and lunch is a voucher (not really, but it feels that way)
Once XML stops being abstract noise, you collide with Tally’s worldview.
Coming from typical web modeling, you might think:
- A customer is a row in a
customerstable. - An invoice is a row in an
invoicestable with child line items. - Payments are another table with foreign keys.
Tally has its own ontology—and it is coherent once you accept it—but it is not a 1:1 import of web CRUD instincts.
One of the early conceptual hurdles for us was understanding how masters and transactions are represented internally in ways that can feel ledger-centric. Customers, items, companies, chart of accounts—these are not “just forms.” They participate in a system where ledger framing is part of the deep grammar.
Then there is the other half: transactions are vouchers.
Invoices, credit notes, payments—voucher becomes the umbrella term. That naming is not wrong, but it is a vocabulary shift. Your brain wants “invoice object.” Tally’s brain wants a voucher type with posting semantics that must agree across taxes, ledgers, line definitions, and masters.
We did not enjoy this learning curve in the moment.
In hindsight, it was one of the best things that happened to the project—because the integration became stable when we stopped translating Tally into ‘web objects’ and started translating Ambill operations into Tally’s native obligations.
If you want a gentle joke to keep morale intact: in Tally’s house, many things wear a ledger-shaped hat. Your job is not to argue with the hat. Your job is to learn the dress code.

Act IV: The connector — web developers build a desktop embassy (lightly terrified)
We were primarily web developers.
We knew how to ship UIs, APIs, background jobs, and cloud databases. We knew how to debug network calls and inspect JSON.
We did not arrive with deep battle scars in desktop agents, local service hosting, Windows server realities, or the subtle psychology of software that must run on a machine you do not control.
But Tally integration required exactly that: a local connector that can:
- Run beside Tally on the machine where Tally is reachable.
- Store configuration safely enough for an enterprise setting.
- Forward requests to Tally over the local integration surface.
- Carry responses back to Ambill for parsing, mapping, persistence, and UI feedback.
- Fail loudly and usefully when Tally is not running, the port is wrong, or the environment is hostile.
So we built a connector.
If you have never shipped a desktop-side agent before, the first time you succeed end-to-end feels like inventing diplomacy: two countries that do not speak the same protocol language suddenly shake hands because someone built a reliable interpreter.
The architecture that was a “tough pill to swallow”
The hardest part was not any single hop. It was the chain.
In a pure web world, your mental model is often:
Browser → API → Database
For Tally Sync, the operational truth looked more like:
Browser → Local Connector → Ambill Backend → Local Connector → Tally → …and then the return path
That is not “a bit more complicated.” That is a multi-relay system where failure can happen at every handoff:
- The browser might not reach the connector (local networking, permissions, SSL assumptions).
- The connector might not reach the cloud (proxy, firewall, DNS, clock skew).
- The backend might generate XML that is almost right (which is the worst kind of wrong).
- Tally might reject XML for a reason that is only visible if you know where to look.
- The response might parse differently across Tally versions or company configurations.
We had to design for observability like we meant it: session identifiers, structured logs, progress signaling, and user-visible breadcrumbs that do not blame the user for being confused—because confusion is the default state when five systems touch the same invoice.
If you want a one-liner that captures the engineering mood: this is not a webhook; this is a relay race where every runner speaks a different dialect and sometimes drops the baton on purpose to test you.

What we built once we accepted the constraints (without pretending Tally is “just another REST resource)
A thin local connector (on purpose)
The connector’s primary virtue is proximity: it can talk to Tally the way Tally expects to be talked to.
We resisted the temptation to make the connector “smart.” Smart agents become undebuggable agents. The connector forwards, authenticates locally as needed, surfaces progress, and returns errors that humans can act on—“Tally not reachable on this port” beats “unknown failure.”
A cloud brain that owns meaning
Ambill remains responsible for the parts we can evolve quickly:
- Billing semantics and operational workflows.
- GST and line-item structure at the Ambill layer.
- Mappings between Ambill records and Tally identifiers so repeat syncs do not duplicate the universe.
- Incremental strategies for masters where “export everything forever” does not scale.
Directionality that matches accounting reality
Not everything should behave like a Google Doc with two-way sync enabled because “sync sounds modern.”
Some masters genuinely drift in both systems; two-way discipline can be correct there—with careful matching rules and human-sane conflict behavior.
Many voucher-like flows are naturally Ambill → Tally: the business event originates in billing, and accounting needs a faithful posting.
Pretending symmetry everywhere creates beautiful demos and ugly reconciliations. We chose boring honesty instead.
Identity: the quiet horror of “maybe this is the same customer”
Once XML starts working, you meet a problem that XML cannot solve: identity.
In a web app, you often trust a primary key. In the real world of accounting, humans rename parties, create near-duplicates under stress, and reuse “almost the same” spellings because someone typed quickly during onboarding.
When you sync customers or items between Ambill and Tally, you are not moving bytes—you are making a claim: this record over here is the same economic actor as that record over there. Wrong claims are not “bugs” in the casual sense. They are reconciliation debt that shows up weeks later when a payment does not tie out.
That is why durable mapping became a first-class idea in our system—not an afterthought. You need a stable way to remember: this Ambill customer maps to that Tally master identity, and you need processes for when the world drifts (merged ledgers, renamed parties, corrected GSTINs).
If you want a joke with teeth: duplicate masters are the integration equivalent of inviting two people with the same nickname to a party and hoping nobody talks to the wrong one.
Incremental reality: why “just re-export everything” is a trap
When you first get XML working, the naive instinct is celebration. The second instinct—often the same day—is dread: how often can we afford to do this?
Full exports are useful for learning. They are brutal for daily operations at scale. Companies grow. Masters change. Vouchers accumulate. If every sync behaves like a archaeological dig through the entire civilization, your connector will look busy while your users lose patience.
So we leaned into incremental thinking for the domains where it applies: track what changed since the last successful conversation, use watermarks that Tally understands (the AlterID-style change tracking that shows up in real exports), and persist sync logs so the next run is a delta, not a rerun of history.
This is also where the giant export files paid dividends twice: first as a Rosetta stone, second as training data for recognizing which fields are stable identifiers vs which fields are cosmetic or computed.
GST is not a “field”; it is a mood
India’s GST rules are not a single number you slap on a line item. Context matters: place of supply, interstate vs intrastate splits, HSN/SAC discipline, rate tables, exemptions, and the way those ideas must line up across headers and lines.
Web developers often think of tax as “a column.” Accounting software thinks of tax as a set of constraints that must remain internally consistent or the voucher refuses to be a good citizen.
That is one reason voucher XML is unforgiving. It is not trying to annoy you. It is trying to protect the books from a class of errors that humans make constantly when they are in a hurry.
We will not turn this blog into a GST textbook—your CA already has opinions—but the integration lesson is universal: when Tally rejects a voucher, assume the rejection might be morally correct even if it is emotionally inconvenient.
Historical migration: past invoices are a different beast than live sync
Live sync is about keeping today aligned.
Historical migration is about importing years without turning your database into a junk museum. Chunk date ranges, match customers with sane fuzzy rules, and treat “could not attach this voucher confidently” as a signal to surface, not a problem to hide.
The same XML literacy that taught us live posting also taught us import humility: if you cannot explain where a voucher landed, you should not pretend you imported it successfully.
Security and trust: the connector is powerful, so it must be boring
A local agent that can talk to your accounting system is not “just a helper.” It is a trust surface.
We approached that with deliberate conservatism: keep the connector thin, keep sensitive business logic in the cloud where you can patch quickly, and treat local configuration as something that must be understandable to IT teams—ports, hosts, credentials, rotation expectations, and clear guidance about what never leaves the machine.
The goal is not flashy security theater. The goal is predictable behavior that an internal security review can reason about without needing heroics.
What we would do differently if we time-traveled (with snacks)
Hindsight is unfair, but it is useful:
- We would start the export-and-diff discipline on day one, even before “feature work,” because it accelerates understanding faster than thrashing on partial examples.
- We would invest earlier in end-to-end tracing across hops—not only server logs, but a user-visible sense of progress for long runs.
- We would write internal docs in the shape of failure catalogs (“these are the top twenty reasons Tally says no”) because that is what support and engineering both need.
None of that replaces courage. It just reduces the volume of courage required per week.
Interlude: a “day in the life” of a sync (told like a story, because it was)
Picture a finance operations person finishing a batch of invoices in Ambill. They click sync. Nothing magical happens instantly in their brain—they have learned that software sometimes lies—so they watch the UI.
The browser asks the local connector to participate. If this step fails, the user is not “bad at computers.” It usually means the connector is not running, the machine changed, or local security got stricter.
The connector calls Ambill’s backend. This is where the cloud earns its keep: decide what needs to happen, generate the correct XML request shapes, attach organization context, and return a coherent instruction package rather than pushing XML construction into the connector (where it would rot).
The connector posts XML to Tally on the configured host/port. If Tally is down, this is where the user gets a crisp, almost rude clarity: no connection. That is better than a silent partial state.
Tally responds with XML that must be parsed with the humility of someone defusing a polite bomb. Sometimes the response is success. Sometimes it is “no,” but at least it is a structured “no.”
The return trip matters as much as the outbound trip: responses go back through the backend for validation and persistence—mappings updated, sync logs advanced, incremental watermarks moved forward—then the UI gets something it can render as progress instead of mysticism.
If you want humor here: this pipeline is basically a group project where every member speaks a different language, and the grade is your month-end close.
The human side: what this felt like in the trenches
Integration projects are not only architecture diagrams. They are also:
- late nights staring at a 9,000-line XML export trying to spot the one tag that explains a rejection;
- a customer IT team asking reasonable questions about ports and security;
- a finance user who did nothing wrong but now sees “sync failed” and assumes the world ended;
- the moment you realize your “simple fix” might create duplicate ledgers if you are careless.
We learned to communicate outwardly with more empathy: the user is not “bad at tech.” The system is legitimately hard because it spans local software, cloud software, accounting semantics, and customer-specific configuration.
We also learned to communicate inwardly with more discipline: no silent failures, no mystery strings, no ‘it works on my machine’ culture for something that explicitly does not live on our machine.
The emotional skill nobody lists on a job description: stamina
There is a particular fatigue that hits around the third night of comparing two enormous XML trees where the diff is technically small but economically huge—a single tag, a single attribute, a single rate table reference.
That fatigue is not laziness. It is the weight of knowing that accounting mistakes are not like UI pixel misalignment. They linger.
The antidote is teamwork humor—brief, not cruel—and rituals: celebrate the first successful voucher, celebrate the first incremental sync that does not duplicate the universe, celebrate the first support ticket you can close in five minutes because logging finally tells the truth.
If you are a lead engineer scheduling this work: do not model it like a normal two-week API integration unless you want your team to quietly resent you. Model it like research plus engineering, with milestones that respect discovery.
Lessons we would give another team attempting the same mountain
1) Budget for “integration archaeology”
If documentation is incomplete, your schedule must include discovery time that looks like research, not “implementation.” The export-and-compare loop is not a detour. It is the main road.
2) Treat XML as a contract, not a template hobby
Hand-built XML taught us respect for semantics. If you generate XML, generate it with the same seriousness you would generate SQL migrations: versioned patterns, tests where possible, and regression checks when Tally or configuration classes change.
3) Design for local failure modes first
Assume Tally is closed. Assume the wrong company is open. Assume the port changed. Assume the connector is outdated. Assume the server rebooted.
If your UX and support playbooks only work when everything is green, you will drown in tickets.
4) Learn Tally’s ontology early
If you fight the ledger/voucher vocabulary, you will build the wrong abstractions and refactor later under pressure.
5) Observability is a feature
Multi-hop integrations without breadcrumbs become religion: faith-based engineering. We prefer engineering where you can point to the hop that failed and fix it.
6) Version drift is real (Tally, Windows, and “the same customer”)
Customers do not move in lockstep. Some lag updates. Some leap ahead. Some run configurations that are valid but uncommon.
Your integration needs a mindset that web-only teams sometimes skip: compatibility is a feature you maintain, not a property you assume.
7) Separate “learning XML” from “shipping XML”
Early on, your workspace will be full of experiments: half-right payloads, commented-out tags, desperate printf-style logging. That is healthy—for a while.
Before you ship broadly, consolidate patterns into reviewable generators (or tightly controlled templates) so you are not playing whack-a-mole across ten copy-pasted string builders.
8) Write for your future support self
The best integration work is invisible. The second-best integration work is visible but actionable: error messages that map to fixes, run IDs that map to logs, and internal docs that explain the top failure modes without requiring a founding engineer’s memory.
FAQ (the questions we wish someone had answered on day zero)
Q: Can we integrate Tally without a local agent?
Often, practically, no—not in the sense of “cloud calls Tally like an API.” You can integrate through a local bridge, a scheduled export/import discipline, or other operational hacks, but you cannot wish away locality if Tally is local.
Q: Is XML “hard”?
XML is not hard. Correct accounting XML under real constraints is hard. The angle brackets are the easy part. The semantics are the tuition fee.
Q: Why not just use CSV import forever?
CSV can be a fine bridge for some workflows. It struggles with ongoing two-way identity, incremental discipline, and rich voucher semantics at scale. If CSV solved everything, you would not be reading a blog about XML and connectors—and we would have slept more.
Q: What is the single biggest misconception?
That integration is “data movement.” It is mostly meaning preservation: identity, tax context, posting rules, and the prevention of duplicates.
Q: What is the second biggest misconception?
That the hard part is the first successful post. The hard part is the hundredth post on a bad Tuesday when Tally is open, the server is slow, and someone renamed three masters between runs.
Closing: why we are still glad we did it
Tally Sync is not “we added a webhook.”
It is the outcome of accepting constraints that feel almost nostalgic in an age of instant APIs: local software, XML, partial documentation, customer-hosted machines, and a data model that rewards humility.
We are proud of the result—not because it was easy, but because it matches how finance teams actually work: Ambill for the velocity of billing and collections, Tally for the gravity of the books—with a connector in the middle doing the unglamorous, essential job of knocking on the right door, in the right room, on the right machine.
If you are reading this as a customer: we built this because your month-end should not depend on copy-paste heroics.
If you are reading this as an engineer: bring snacks, export XML early, and remember that the integration is not the JSON. Sometimes it is a ten-thousand-line scroll and the stubborn belief that you can learn anything if you stare long enough.
