TallyPrime Part 24: XML Integration Deep Dive — Envelope, Masters, Vouchers, HTTP API & Production Integration Service | FreeLearning365

TallyPrime Part 24: XML Integration Deep Dive — Envelope, Masters, Vouchers, HTTP API & Production Integration Service | FreeLearning365
📡
🔌
🔗
Part 24 of 45 — TallyPrime Deep-Dive Masterclass

XML Integration Deep Dive: The Developer's Bridge to TallyPrime

Master TallyPrime's primary integration interface — XML over HTTP. Complete envelope structure, creating ledgers, stock items, sales vouchers, receipts, and payments. Plus production-ready error handling, idempotency patterns, security, and a full ASP.NET Core integration service.

📖 160 min read 📚 14 Chapters 📡 Envelope Deep Dive 🔌 Voucher Creation ⚙️ Error Handling 🛡️ Production Service
1

XML Integration Overview

Why XML is Tally's primary integration interface and what you can build with it

Every TallyPrime integration you have ever used — payment gateway sync, e-commerce order push, CRM customer sync, WhatsApp invoice send, Power BI data pull — is built on one foundation: XML over HTTP. This is Tally's most mature, most feature-complete, and most powerful integration interface.

🏗️ The One-Line Definition: XML integration is a way for external applications to talk to TallyPrime by sending HTTP POST requests containing structured XML, and receiving XML responses with data or status codes.

What XML Integration Can Do

  • Create Masters: Ledgers, Stock Items, Stock Groups, Units, Godowns, Cost Centres
  • Create Vouchers: Sales, Purchase, Receipt, Payment, Contra, Journal, Credit Note, Debit Note
  • Read Masters: Fetch customer list, supplier list, stock item list
  • Read Vouchers: Fetch all transactions for a date range
  • Read Reports: Outstanding, Sales Register, Purchase Register, Trial Balance
  • Trigger TDL Functions: Execute custom logic defined in TDL
  • Bulk Operations: Push hundreds of vouchers in a single request

XML vs Other Integration Options

InterfaceReadWriteMaturityBest For
XML over HTTP Yes Yes 20+ years Full integration — create vouchers, masters, read reports
JSON over HTTP Yes Yes Recent Modern web apps, REST APIs
ODBC Yes No Mature Analytics, Power BI, Excel, data science

The Request/Response Model

Your Application / ERP
HTTP POST · XML Payload
▼
TallyPrime HTTP Server · Port 9000
Parse · Validate · Execute
▼
Tally Data Engine
HTTP Response · XML Status
▼
Success / Failure / Data
1
Request At A Time (single-threaded)
9000
Default Port
100s
Vouchers Per Batch (recommended)
0
Cost (built into TallyPrime)
📡
Real-World Impact: A single XML integration service can push 50,000+ vouchers per day from a custom ERP into TallyPrime. This is how large Bangladeshi distributors and manufacturers keep their operational systems and Tally accounting in sync — without any manual data entry.

When to Use XML Integration

  • Your sales orders are captured in a custom ERP, e-commerce platform, or CRM
  • Your payment gateway processes transactions that need to flow into Tally
  • Your bank statements need to be auto-imported as receipt/payment vouchers
  • Your warehouse system records stock movements that must post to Tally
  • Your BI dashboards need real-time data pulled from Tally
  • Your mobile app needs to create invoices directly in Tally
  • Your multi-branch operations need central Tally consolidation

When NOT to Use XML Integration

✗ Avoid XML Integration When
  • You need real-time sub-second response times
  • You need parallel multi-user writes at scale
  • You need complex relational queries
  • You are building real-time dashboards that refresh every second
  • You need two-way sync with conflict resolution
✓ Use XML Integration When
  • You need to push/pull data reliably (not real-time)
  • You can batch and queue operations
  • You need to create vouchers and masters
  • You need to read reports and export data
  • You need a stable, documented interface
2

The XML Envelope — Anatomy & Structure

Every XML request uses the same envelope. Master it once, use it everywhere.

Every XML request to TallyPrime is wrapped in an ENVELOPE. Think of it like a physical envelope: the outer envelope has addressing information (HEADER) and content (BODY). Tally opens the envelope, reads the header, then processes the body.

The Complete Envelope Structure

📨
<ENVELOPE> — Outer wrapper for every request
📋
<HEADER> — Metadata about the request
📦
<BODY> — The actual data or query
⚙️
<REQUESTDESC> — Request description (report name, static variables)
💾
<REQUESTDATA> — The payload (vouchers, masters, etc.)

Minimal Import Envelope

<ENVELOPE>
  <HEADER>
    <TALLYREQUEST>Import Data</TALLYREQUEST>
  </HEADER>
  <BODY>
    <IMPORTDATA>
      <REQUESTDESC>
        <REPORTNAME>Vouchers</REPORTNAME>
        <STATICVARIABLES>
          <SVCURRENTCOMPANY>ABC Ltd.</SVCURRENTCOMPANY>
        </STATICVARIABLES>
      </REQUESTDESC>
      <REQUESTDATA>
        <!-- Your vouchers, ledgers, or stock items go here -->
      </REQUESTDATA>
    </IMPORTDATA>
  </BODY>
</ENVELOPE>

Minimal Export Envelope

<ENVELOPE>
  <HEADER>
    <TALLYREQUEST>Export Data</TALLYREQUEST>
  </HEADER>
  <BODY>
    <EXPORTDATA>
      <REQUESTDESC>
        <REPORTNAME>List of Ledgers</REPORTNAME>
        <STATICVARIABLES>
          <SVCURRENTCOMPANY>ABC Ltd.</SVCURRENTCOMPANY>
        </STATICVARIABLES>
      </REQUESTDESC>
    </EXPORTDATA>
  </BODY>
</ENVELOPE>

Envelope Element Reference

ElementPurposeRequired
<ENVELOPE> Root wrapper — always the outermost element Yes
<HEADER> Contains TALLYREQUEST type Yes
<TALLYREQUEST> Import Data, Export Data, or Execute Yes
<BODY> Contains the actual request Yes
<IMPORTDATA> Wrapper for import operations For imports
<EXPORTDATA> Wrapper for export operations For exports
<REQUESTDESC> Describes what to import/export Yes
<REPORTNAME> Vouchers, Ledgers, StockItems, etc. Yes
<STATICVARIABLES> Report parameters (company, dates, filters) Recommended
<SVCURRENTCOMPANY> The Tally company to operate on Required if multiple companies
<REQUESTDATA> The actual payload (import only) For imports
<TALLYMESSAGE> Wraps each object being imported For imports
💻 Developer Tip: Envelopes are case-sensitive in some contexts. Always use <ENVELOPE> not <envelope>. Same for <HEADER>, <BODY>, <TALLYREQUEST>. Tally is generally lenient but strict casing avoids unpredictable errors.

Common STATICVARIABLES

  • SVCURRENTCOMPANY — The company name (required if multiple)
  • SVFROMDATE — Start date for the report (YYYYMMDD format)
  • SVTODATE — End date for the report
  • SVEXPORTFORMAT — Export format (XML, CSV, etc.)
  • SVSHOWLEDGERDETAILS — Show detailed ledger entries
  • SVINVENTORYDETAILS — Include inventory details
  • SVCostCentre — Filter by cost centre
⚠️ Date Format: Tally accepts dates in YYYYMMDD format (e.g., 20260918) in STATICVARIABLES. In voucher payloads, you can use YYYYMMDD or DD-MMM-YYYY. Consistency matters — pick one format and stick with it.
3

HTTP Setup & Configuration

Enable the HTTP server in TallyPrime, configure the port, and test connectivity

Before you send your first XML request, you must enable TallyPrime's HTTP server. This is what listens on port 9000 and processes incoming XML requests.

Step 1 — Enable Tally's HTTP Server

  1. Open TallyPrime with a company loaded
  2. Press F1 → Settings → Connectivity
  3. Under Tally.NET Server, set the following:
    • Tally.NET Server: Both (Client and Server)
    • Port: 9000 (default; change if there is a conflict)
  4. Press Ctrl+A to save
  5. Restart TallyPrime for the settings to take effect
💡 Alternative Configuration: You can also enable this from F1 → Settings → Advanced Configuration. Look for Tally.NET Server Port and Enable ODBC Server. Both XML and ODBC share the same port.

Step 2 — Configure Windows Firewall

If your integration service runs on a different machine than TallyPrime, you must allow incoming traffic on port 9000.

🛡️ Firewall Configuration
  • Open Windows Defender Firewall → Advanced Settings
  • Click Inbound Rules → New Rule
  • Choose Port → TCP → Specific local port: 9000
  • Choose Allow the connection
  • Apply to Domain and Private networks only (never Public)
  • Name the rule "TallyPrime XML" and save
🚨 Security Critical: Never open port 9000 to the public internet. Tally's XML server has no built-in authentication. Always keep it behind a firewall, VPN, or reverse proxy. If you need external access, use a properly secured middleware with HTTPS, authentication, and rate limiting.

Step 3 — Test Connectivity

The simplest test: use curl or Postman to send a minimal XML request.

# Windows curl (PowerShell) — Test HTTP connectivity
$body = @"
<ENVELOPE>
  <HEADER>
    <TALLYREQUEST>Export Data</TALLYREQUEST>
  </HEADER>
  <BODY>
    <EXPORTDATA>
      <REQUESTDESC>
        <REPORTNAME>List of Companies</REPORTNAME>
      </REQUESTDESC>
    </EXPORTDATA>
  </BODY>
</ENVELOPE>
"@

Invoke-WebRequest -Uri "http://localhost:9000" -Method POST -Body $body -ContentType "text/xml"
# Linux / macOS curl
curl -X POST http://localhost:9000 \
  -H "Content-Type: text/xml" \
  -d '<ENVELOPE><HEADER><TALLYREQUEST>Export Data</TALLYREQUEST></HEADER><BODY><EXPORTDATA><REQUESTDESC><REPORTNAME>List of Companies</REPORTNAME></REQUESTDESC></EXPORTDATA></BODY></ENVELOPE>'

If you get back a valid XML response listing companies, the HTTP server is working. If you get connection refused, Tally is not running or the HTTP server is not enabled.

Common Configuration Issues

IssueSymptomFix
Connection refused Cannot connect to port 9000 Enable Tally.NET Server in Settings · Restart Tally · Check firewall
Port conflict Tally does not start HTTP server Change port in Advanced Configuration to 9001 or similar
Company not loaded Returns "Company not loaded" error Load the company in Tally UI first · Or specify SVCURRENTCOMPANY in STATICVARIABLES
Timeout on large payloads Request hangs then fails Reduce batch size · Increase HTTP timeout in your client
Encoding errors Garbled characters in response Send payload as UTF-16LE if using Tally Gateway · Or use UTF-8 for direct HTTP
💻 Multiple Companies: If you have multiple companies loaded in Tally, always specify <SVCURRENTCOMPANY> in STATICVARIABLES. Without it, Tally uses the currently active company in the UI, which can cause confusing errors.

Recommended Tools

  • Postman — Best for interactive testing and exploring requests
  • curl — Quick command-line tests
  • Insomnia — Alternative to Postman
  • Fiddler — Inspect HTTP traffic in detail
  • Wireshark — Deep packet inspection (rarely needed)
4

Request Types — Import, Export, Execute

The three TALLYREQUEST types and when to use each

The <TALLYREQUEST> element tells Tally what kind of operation you are performing. There are three main types:

1. Import Data — Write to Tally

📥 Import Data

Use this to create or alter objects in Tally: vouchers, ledgers, stock items, groups, etc.

  • Creates new vouchers, ledgers, or stock items
  • Can modify existing objects if you set ACTION="Alter"
  • Can delete objects if you set ACTION="Delete"
  • Returns success/failure status for each object

2. Export Data — Read from Tally

📤 Export Data

Use this to retrieve data from Tally: lists of masters, vouchers, or reports.

  • Returns lists of masters (ledgers, stock items, groups)
  • Returns voucher data by date range
  • Returns report output (Outstanding, Trial Balance, etc.)
  • Always read-only — never modifies anything

3. Execute — Trigger TDL Function

⚡ Execute

Use this to run a TDL function defined in a custom TDL file.

  • Triggers custom logic defined in TDL
  • Useful for complex operations that need TDL features
  • Can pass parameters via XML
  • Can return values via XML response

Comparison

Request TypePurposeRead/WritePayload
Import Data Create/alter objects Write Vouchers, Ledgers, Stock Items
Export Data Retrieve data Read Report definition only
Execute Run TDL function Either Function name and parameters
💻 Developer Pattern: Your integration service will typically use both Import and Export. Example: Before creating a customer, first Export the list of existing customers to check for duplicates. Then Import the new customer only if it does not exist.
5

Creating Ledgers via XML

Adding customer, supplier, bank, and expense ledgers programmatically

Ledgers are the foundation of Tally accounting. Before you can push vouchers, the relevant ledgers must exist. Here is how to create them via XML.

Creating a Customer Ledger (Sundry Debtor)

<ENVELOPE>
  <HEADER>
    <TALLYREQUEST>Import Data</TALLYREQUEST>
  </HEADER>
  <BODY>
    <IMPORTDATA>
      <REQUESTDESC>
        <REPORTNAME>All Masters</REPORTNAME>
        <STATICVARIABLES>
          <SVCURRENTCOMPANY>ABC Ltd.</SVCURRENTCOMPANY>
        </STATICVARIABLES>
      </REQUESTDESC>
      <REQUESTDATA>
        <TALLYMESSAGE xmlns:UDF="TallyUDF">
          <LEDGER NAME="Rahim Traders" ACTION="Create">
            <NAME>Rahim Traders</NAME>
            <PARENT>Sundry Debtors</PARENT>
            <ISBILLWISEON>Yes</ISBILLWISEON>
            <CREDITLIMIT>500000</CREDITLIMIT>
            <CREDITDAYS>30</CREDITDAYS>
            <LEDGERPHONE>+8801700000000</LEDGERPHONE>
            <LEDGEREMAIL>rahim@example.com</LEDGEREMAIL>
            <MAILINGNAME>Rahim Traders</MAILINGNAME>
            <ADDRESS>123 Motijheel, Dhaka</ADDRESS>
            <COUNTRYNAME>Bangladesh</COUNTRYNAME>
            <LEDGSTREGISTRATIONNUMBER>123456789012</LEDGSTREGISTRATIONNUMBER>
            <UDF:REGION>Dhaka</UDF:REGION>
            <UDF:CREDITSCORE>750</UDF:CREDITSCORE>
          </LEDGER>
        </TALLYMESSAGE>
      </REQUESTDATA>
    </IMPORTDATA>
  </BODY>
</ENVELOPE>

Creating a Supplier Ledger (Sundry Creditor)

<LEDGER NAME="Sunflower Oil Industries" ACTION="Create">
  <NAME>Sunflower Oil Industries</NAME>
  <PARENT>Sundry Creditors</PARENT>
  <ISBILLWISEON>Yes</ISBILLWISEON>
  <CREDITDAYS>45</CREDITDAYS>
  <LEDGERPHONE>+8801800000000</LEDGERPHONE>
  <LEDGSTREGISTRATIONNUMBER>987654321098</LEDGSTREGISTRATIONNUMBER>
  <ADDRESS>Industrial Area, Chittagong</ADDRESS>
  <COUNTRYNAME>Bangladesh</COUNTRYNAME>
  <ISGSTAPPLICABLE>Yes</ISGSTAPPLICABLE>
</LEDGER>

Creating a Bank Ledger

<LEDGER NAME="SBI Bank Current A/c" ACTION="Create">
  <NAME>SBI Bank Current A/c</NAME>
  <PARENT>Bank Accounts</PARENT>
  <ISBANKACCOUNT>Yes</ISBANKACCOUNT>
  <BANKACCOUNTNUMBER>1234567890</BANKACCOUNTNUMBER>
  <BANKACCOUNTHOLDER>ABC Ltd.</BANKACCOUNTHOLDER>
  <IFSCODE>SBIN0001234</IFSCODE>
  <BRANCHNAME>Motijheel</BRANCHNAME>
  <MICRCODE>123456789</MICRCODE>
  <ISBILLWISEON>No</ISBILLWISEON>
</LEDGER>

Creating an Expense Ledger

<LEDGER NAME="Office Rent" ACTION="Create">
  <NAME>Office Rent</NAME>
  <PARENT>Indirect Expenses</PARENT>
  <ISBILLWISEON>No</ISBILLWISEON>
  <ISCOSTCENTRESON>Yes</ISCOSTCENTRESON>
  <ISGSTAPPLICABLE>No</ISGSTAPPLICABLE>
</LEDGER>

Key Ledger Attributes

AttributePurposeValues
<NAME>Ledger nameString
<PARENT>Parent groupSundry Debtors, Sundry Creditors, Bank Accounts, etc.
<ISBILLWISEON>Enable bill-wise trackingYes / No
<CREDITLIMIT>Maximum credit allowedNumber
<CREDITDAYS>Credit period in daysNumber
<LEDGSTREGISTRATIONNUMBER>VAT/GST registrationString
<ISGSTAPPLICABLE>GST applicable?Yes / No
<ISBANKACCOUNT>Is bank ledger?Yes / No
<BANKACCOUNTNUMBER>Bank account numberString
<IFSCODE>Bank IFSC (India)String
<ISCOSTCENTRESON>Cost centre trackingYes / No
💡 Idempotency Pattern for Ledgers: Use ACTION="Alter" with the same NAME to make the operation idempotent. If the ledger exists, it will be updated; if not, Tally creates it. This way, repeated pushes do not cause errors.
6

Creating Stock Items, Units & Groups

Master data for inventory — items, units of measure, and stock groups

Inventory integration requires three types of masters: Units (of measure), Stock Groups (categories), and Stock Items (actual products).

Creating Units of Measure

<UNIT NAME="Pcs" ACTION="Create">
  <NAME>Pcs</NAME>
  <ISSIMPLEUNIT>Yes</ISSIMPLEUNIT>
  <DECIMALPLACES>0</DECIMALPLACES>
</UNIT>

<UNIT NAME="Kg" ACTION="Create">
  <NAME>Kg</NAME>
  <ISSIMPLEUNIT>Yes</ISSIMPLEUNIT>
  <DECIMALPLACES>3</DECIMALPLACES>
</UNIT>

Creating Stock Groups

<STOCKGROUP NAME="Electronics" ACTION="Create">
  <NAME>Electronics</NAME>
  <PARENT>Primary</PARENT>
</STOCKGROUP>

<STOCKGROUP NAME="Mobile Phones" ACTION="Create">
  <NAME>Mobile Phones</NAME>
  <PARENT>Electronics</PARENT>
</STOCKGROUP>

Creating a Stock Item

<STOCKITEM NAME="Samsung Galaxy A54" ACTION="Create">
  <NAME>Samsung Galaxy A54</NAME>
  <PARENT>Mobile Phones</PARENT>
  <BASEUNITS>Pcs</BASEUNITS>
  <OPENINGBALANCE>50 Pcs</OPENINGBALANCE>
  <OPENINGRATE>35000</OPENINGRATE>
  <OPENINGVALUE>1750000</OPENINGVALUE>
  <GSTAPPLICABLE>Applicable</GSTAPPLICABLE>
  <GSTTYPEOFSUPPLY>Goods</GSTTYPEOFSUPPLY>
  <HSNCODE>85171200</HSNCODE>
  <GSTOVRDNTAXABILITY>Taxable</GSTOVRDNTAXABILITY>
  <GSTRATEDETAILS.LIST>
    <GSTRATE>15</GSTRATE>
  </GSTRATEDETAILS.LIST>
  <UDF:MANUFACTURER>Samsung</UDF:MANUFACTURER>
  <UDF:WARRANTY>12 months</UDF:WARRANTY>
</STOCKITEM>

Key Stock Item Attributes

AttributePurpose
<NAME>Item name
<PARENT>Stock group
<BASEUNITS>Unit of measure
<OPENINGBALANCE>Opening quantity
<OPENINGRATE>Opening rate per unit
<OPENINGVALUE>Total opening value
<HSNCODE>HSN code for GST
<GSTRATEDETAILS.LIST>GST rate structure
<UDF:XXX>User-defined fields
💻 Order of Creation: Always create Units → Stock Groups → Stock Items, in that order. Stock Items reference both Units and Groups, so those must exist first. Same for vouchers — masters before transactions.
7

Creating Sales Vouchers via XML

The most common integration scenario — pushing sales invoices with inventory, tax, and bill allocation

Sales vouchers are the most frequently pushed objects from ERP to Tally. A properly constructed Sales voucher includes: ledger entries (customer, sales account, tax), inventory entries (stock items), and bill allocations (invoice reference).

Complete Sales Voucher — Accounting + Inventory

<VOUCHER VCHTYPE="Sales" ACTION="Create">
  <DATE>20260918</DATE>
  <EFFECTIVEDATE>20260918</EFFECTIVEDATE>
  <VOUCHERNUMBER>SI-2026-0042</VOUCHERNUMBER>
  <REFERENCE>ERP-INV-98765</REFERENCE>
  <!-- Idempotency key from source ERP -->
  <VOUCHERTYPENAME>Sales</VOUCHERTYPENAME>
  <PARTYLEDGERNAME>Rahim Traders</PARTYLEDGERNAME>
  <NARRATION>Sales as per PO #PO-2026-1234</NARRATION>
  <PERSISTEDVIEW>Invoice Voucher View</PERSISTEDVIEW>
  <ISINVOICE>Yes</ISINVOICE>

  <!-- ACCOUNTING ENTRIES -->
  <!-- Customer Debit (positive = debit) -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Rahim Traders</LEDGERNAME>
    <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
    <AMOUNT>414000.00</AMOUNT>
    <BILLALLOCATIONS.LIST>
      <NAME>SI-2026-0042</NAME>
      <BILLTYPE>New Ref</BILLTYPE>
      <AMOUNT>414000.00</AMOUNT>
    </BILLALLOCATIONS.LIST>
  </ALLLEDGERENTRIES.LIST>

  <!-- Sales Account Credit (negative = credit) -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Sales Account</LEDGERNAME>
    <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
    <AMOUNT>-360000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>

  <!-- Output GST Credit -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Output GST 15%</LEDGERNAME>
    <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
    <AMOUNT>-54000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>

  <!-- INVENTORY ENTRIES -->
  <ALLINVENTORYENTRIES.LIST>
    <STOCKITEMNAME>Samsung Galaxy A54</STOCKITEMNAME>
    <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
    <RATE>36000/Pcs</RATE>
    <AMOUNT>-360000.00</AMOUNT>
    <ACTUALQTY>10 Pcs</ACTUALQTY>
    <BILLEDQTY>10 Pcs</BILLEDQTY>
    <ACCOUNTINGALLOCATIONS.LIST>
      <LEDGERNAME>Sales Account</LEDGERNAME>
      <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
      <AMOUNT>-360000.00</AMOUNT>
    </ACCOUNTINGALLOCATIONS.LIST>
  </ALLINVENTORYENTRIES.LIST>
</VOUCHER>

Understanding the Debit/Credit Signs

Ledger EntryISDEEMEDPOSITIVEAMOUNT Sign
Debit (e.g., Customer) Yes Positive (+)
Credit (e.g., Sales, GST) No Negative (−)
⚠️ The #1 Sign Confusion: In Tally XML, debits are positive and credits are negative. This is opposite to how most people think about accounting. Always check: a Sales invoice debits the customer (positive) and credits Sales (negative). If the signs are wrong, the voucher will fail validation.

Sales Voucher with Multiple Items

<!-- Two inventory entries, same voucher -->
<ALLINVENTORYENTRIES.LIST>
  <STOCKITEMNAME>Samsung Galaxy A54</STOCKITEMNAME>
  <RATE>36000/Pcs</RATE>
  <AMOUNT>-360000.00</AMOUNT>
  <ACTUALQTY>10 Pcs</ACTUALQTY>
  <BILLEDQTY>10 Pcs</BILLEDQTY>
  <ACCOUNTINGALLOCATIONS.LIST>
    <LEDGERNAME>Sales Account</LEDGERNAME>
    <AMOUNT>-360000.00</AMOUNT>
  </ACCOUNTINGALLOCATIONS.LIST>
</ALLINVENTORYENTRIES.LIST>

<ALLINVENTORYENTRIES.LIST>
  <STOCKITEMNAME>Samsung Charger 25W</STOCKITEMNAME>
  <RATE>1800/Pcs</RATE>
  <AMOUNT>-18000.00</AMOUNT>
  <ACTUALQTY>10 Pcs</ACTUALQTY>
  <BILLEDQTY>10 Pcs</BILLEDQTY>
  <ACCOUNTINGALLOCATIONS.LIST>
    <LEDGERNAME>Sales Account</LEDGERNAME>
    <AMOUNT>-18000.00</AMOUNT>
  </ACCOUNTINGALLOCATIONS.LIST>
</ALLINVENTORYENTRIES.LIST>
💻 Validation Rule: The sum of all debit amounts (positive) must equal the sum of all credit amounts (absolute). If customer debit is 414,000, then the sum of credits (Sales 360,000 + GST 54,000) must be 414,000. Off by even 1 paisa, and Tally rejects the voucher.
8

Creating Purchase Vouchers

Supplier invoices with input tax credit, inventory receipt, and bill-wise tracking

Purchase vouchers are structurally similar to Sales vouchers, but the debit/credit signs are reversed: supplier is credited (negative), purchase account is debited (positive), and input tax is debited (positive).

Complete Purchase Voucher

<VOUCHER VCHTYPE="Purchase" ACTION="Create">
  <DATE>20260918</DATE>
  <VOUCHERNUMBER>PI-2026-0087</VOUCHERNUMBER>
  <REFERENCE>ERP-PO-45678</REFERENCE>
  <VOUCHERTYPENAME>Purchase</VOUCHERTYPENAME>
  <PARTYLEDGERNAME>Sunflower Oil Industries</PARTYLEDGERNAME>
  <NARRATION>Purchase against PO #45678</NARRATION>
  <PERSISTEDVIEW>Invoice Voucher View</PERSISTEDVIEW>
  <ISINVOICE>Yes</ISINVOICE>

  <!-- Supplier Credit (negative = credit) -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Sunflower Oil Industries</LEDGERNAME>
    <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
    <AMOUNT>-230000.00</AMOUNT>
    <BILLALLOCATIONS.LIST>
      <NAME>PI-2026-0087</NAME>
      <BILLTYPE>New Ref</BILLTYPE>
      <AMOUNT>230000.00</AMOUNT>
    </BILLALLOCATIONS.LIST>
  </ALLLEDGERENTRIES.LIST>

  <!-- Purchase Account Debit -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Purchase Account</LEDGERNAME>
    <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
    <AMOUNT>200000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>

  <!-- Input GST Debit -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Input GST 15%</LEDGERNAME>
    <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
    <AMOUNT>30000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>

  <!-- Inventory Entry -->
  <ALLINVENTORYENTRIES.LIST>
    <STOCKITEMNAME>Sunflower Oil 1L Bottle</STOCKITEMNAME>
    <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
    <RATE>200/Bottle</RATE>
    <AMOUNT>200000.00</AMOUNT>
    <ACTUALQTY>1000 Bottle</ACTUALQTY>
    <BILLEDQTY>1000 Bottle</BILLEDQTY>
    <ACCOUNTINGALLOCATIONS.LIST>
      <LEDGERNAME>Purchase Account</LEDGERNAME>
      <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
      <AMOUNT>200000.00</AMOUNT>
    </ACCOUNTINGALLOCATIONS.LIST>
  </ALLINVENTORYENTRIES.LIST>
</VOUCHER>

Purchase vs Sales — Sign Comparison

EntrySales VoucherPurchase Voucher
Party Ledger Debit (+) Credit (−)
Sales/Purchase A/c Credit (−) Debit (+)
Output/Input GST Credit (−) Debit (+)
Inventory Entry Credit (−) Debit (+)
💡 Memory Aid: In Tally XML, think of ISDEEMEDPOSITIVE as "is this a debit?" Debit = Yes = positive amount. Credit = No = negative amount. This simple rule works for every voucher type.
9

Creating Receipts, Payments & Contra

Cash and bank transactions with bill-wise allocation for proper invoice closure

Receipts and payments move cash between bank/cash accounts and party ledgers. Proper bill allocation is essential — without it, invoices remain open in the ageing report even after payment.

Full Receipt Against Invoice

<VOUCHER VCHTYPE="Receipt" ACTION="Create">
  <DATE>20260918</DATE>
  <VOUCHERNUMBER>RCP-2026-0158</VOUCHERNUMBER>
  <REFERENCE>BEFTN-987654</REFERENCE>
  <VOUCHERTYPENAME>Receipt</VOUCHERTYPENAME>
  <NARRATION>Payment received via BEFTN</NARRATION>

  <!-- Bank Debit -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>SBI Bank Current A/c</LEDGERNAME>
    <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
    <AMOUNT>414000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>

  <!-- Customer Credit with Bill Allocation -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Rahim Traders</LEDGERNAME>
    <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
    <AMOUNT>-414000.00</AMOUNT>
    <BILLALLOCATIONS.LIST>
      <NAME>SI-2026-0042</NAME>
      <BILLTYPE>Agst Ref</BILLTYPE>
      <AMOUNT>414000.00</AMOUNT>
    </BILLALLOCATIONS.LIST>
  </ALLLEDGERENTRIES.LIST>
</VOUCHER>

Partial Receipt with Multiple Bill Allocations

<ALLLEDGERENTRIES.LIST>
  <LEDGERNAME>Rahim Traders</LEDGERNAME>
  <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
  <AMOUNT>-100000.00</AMOUNT>
  <!-- Allocation 1: Close first invoice fully -->
  <BILLALLOCATIONS.LIST>
    <NAME>SI-2026-0042</NAME>
    <BILLTYPE>Agst Ref</BILLTYPE>
    <AMOUNT>57500.00</AMOUNT>
  </BILLALLOCATIONS.LIST>
  <!-- Allocation 2: Partial against second -->
  <BILLALLOCATIONS.LIST>
    <NAME>SI-2026-0058</NAME>
    <BILLTYPE>Agst Ref</BILLTYPE>
    <AMOUNT>42500.00</AMOUNT>
  </BILLALLOCATIONS.LIST>
</ALLLEDGERENTRIES.LIST>

Payment to Supplier

<VOUCHER VCHTYPE="Payment" ACTION="Create">
  <DATE>20260918</DATE>
  <VOUCHERNUMBER>PAY-2026-0092</VOUCHERNUMBER>
  <REFERENCE>BEFTN-987700</REFERENCE>
  <VOUCHERTYPENAME>Payment</VOUCHERTYPENAME>
  <NARRATION>Payment to supplier</NARRATION>

  <!-- Supplier Debit -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Sunflower Oil Industries</LEDGERNAME>
    <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
    <AMOUNT>230000.00</AMOUNT>
    <BILLALLOCATIONS.LIST>
      <NAME>PI-2026-0087</NAME>
      <BILLTYPE>Agst Ref</BILLTYPE>
      <AMOUNT>230000.00</AMOUNT>
    </BILLALLOCATIONS.LIST>
  </ALLLEDGERENTRIES.LIST>

  <!-- Bank Credit -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>SBI Bank Current A/c</LEDGERNAME>
    <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
    <AMOUNT>-230000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>
</VOUCHER>

Contra Entry (Bank to Cash)

<VOUCHER VCHTYPE="Contra" ACTION="Create">
  <DATE>20260918</DATE>
  <VOUCHERNUMBER>CON-2026-0005</VOUCHERNUMBER>
  <VOUCHERTYPENAME>Contra</VOUCHERTYPENAME>
  <NARRATION>Cash withdrawn from bank</NARRATION>

  <!-- Cash Debit (cash increases) -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>Cash-in-Hand</LEDGERNAME>
    <ISDEEMEDPOSITIVE>Yes</ISDEEMEDPOSITIVE>
    <AMOUNT>50000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>

  <!-- Bank Credit (bank decreases) -->
  <ALLLEDGERENTRIES.LIST>
    <LEDGERNAME>SBI Bank Current A/c</LEDGERNAME>
    <ISDEEMEDPOSITIVE>No</ISDEEMEDPOSITIVE>
    <AMOUNT>-50000.00</AMOUNT>
  </ALLLEDGERENTRIES.LIST>
</VOUCHER>

Bill Type Reference

BILLTYPEWhen to Use
New RefCreating a new invoice/reference (Sales, Purchase invoices)
Agst RefSettling against an existing invoice (Receipts, Payments)
AdvanceRecording an advance paid/received before an invoice
On AccountPayment that cannot be tied to a specific bill
⚠️ Bill Allocation Critical: If you push a Receipt without <BILLALLOCATIONS.LIST>, Tally records the receipt against the customer's overall balance but leaves every invoice open in the ageing report. The customer's outstanding appears correct in total, but the bill-wise report is broken. Always include bill allocation.
10

Reading Data — Export Requests

Retrieving ledgers, vouchers, outstanding, and reports from Tally via XML

Reading data from Tally uses the Export Data request type. This is how your ERP, CRM, or BI dashboard stays in sync with Tally's master data and transactions.

Export List of Ledgers

<ENVELOPE>
  <HEADER>
    <TALLYREQUEST>Export Data</TALLYREQUEST>
  </HEADER>
  <BODY>
    <EXPORTDATA>
      <REQUESTDESC>
        <REPORTNAME>List of Ledgers</REPORTNAME>
        <STATICVARIABLES>
          <SVCURRENTCOMPANY>ABC Ltd.</SVCURRENTCOMPANY>
        </STATICVARIABLES>
      </REQUESTDESC>
    </EXPORTDATA>
  </BODY>
</ENVELOPE>

Export Vouchers for Date Range

<EXPORTDATA>
  <REQUESTDESC>
    <REPORTNAME>Voucher Register</REPORTNAME>
    <STATICVARIABLES>
      <SVCURRENTCOMPANY>ABC Ltd.</SVCURRENTCOMPANY>
      <SVFROMDATE>20260401</SVFROMDATE>
      <SVTODATE>20260918</SVTODATE>
      <VOUCHERTYPEFILTER>Sales</VOUCHERTYPEFILTER>
    </STATICVARIABLES>
  </REQUESTDESC>
</EXPORTDATA>

Export Outstanding Receivables

<EXPORTDATA>
  <REQUESTDESC>
    <REPORTNAME>Outstandings Receivables</REPORTNAME>
    <STATICVARIABLES>
      <SVCURRENTCOMPANY>ABC Ltd.</SVCURRENTCOMPANY>
      <SVFROMDATE>20260401</SVFROMDATE>
      <SVTODATE>20260918</SVTODATE>
      <AGINGMETHOD>Due Date</AGINGMETHOD>
      <AGINGBUCKETS>0,30,60,90,180</AGINGBUCKETS>
    </STATICVARIABLES>
  </REQUESTDESC>
</EXPORTDATA>

Common Export Report Names

Report NameWhat It Returns
List of LedgersAll ledgers with their groups
List of Stock ItemsAll stock items
List of GroupsAll groups
List of CompaniesAll companies loaded in Tally
Voucher RegisterVouchers for a date range
Day BookAll vouchers for a day/period
Outstandings ReceivablesCustomer-wise outstanding
Outstandings PayablesSupplier-wise outstanding
Trial BalanceLedger balances
Profit & LossP&L report
Balance SheetBalance Sheet
Stock SummaryStock item balances
💻 Export Response Format: Tally responds with a large XML document containing all matching objects. For reports with thousands of rows, the response can be several megabytes. Use streaming XML parsers (SAX, XmlReader) rather than loading the entire response into memory.
11

Error Handling, Idempotency & Retry

Production-grade patterns for reliable, duplicate-free Tally integration

XML integration in production is not about sending the perfect request. It is about handling failures gracefully — duplicate prevention, error parsing, retry logic, and reconciliation. This is where integration projects succeed or fail.

Tally Response Structure

When you push a voucher, Tally responds with an XML document. Always parse it to confirm success.

<!-- Success Response -->
<RESPONSE>
  <LINEERROR></LINEERROR>
  <CREATED>1</CREATED>
  <ALTERED>0</ALTERED>
  <IGNORED>0</IGNORED>
  <ERRORS>0</ERRORS>
  <EXCEPTIONS>0</EXCEPTIONS>
</RESPONSE>

<!-- Failure Response -->
<RESPONSE>
  <LINEERROR>Ledger 'Rahim Traders' does not exist!</LINEERROR>
  <CREATED>0</CREATED>
  <ERRORS>1</ERRORS>
</RESPONSE>

Common Error Messages

ErrorCauseFix
Ledger does not exist Party ledger not created Create ledger first or add to payload
Stock Item does not exist Stock item not created Create stock item first
Voucher totals mismatch Debits ≠ Credits Check signs — debits positive, credits negative
Duplicate voucher number Voucher number already exists Use unique voucher numbers or ACTION=Alter
Invalid date format Date not in YYYYMMDD Send date as YYYYMMDD or DD-MMM-YYYY
Company not loaded SVCURRENTCOMPANY not specified Add company name to STATICVARIABLES
Amounts not balanced Rounding differences Ensure debit total equals credit total exactly
GST ledger not found Tax ledger does not exist Create tax ledgers first

The Idempotency Pattern

Idempotency means: running the same operation twice produces the same result. Without this, a network retry can push the same voucher twice, creating duplicates that break your books.

1. ERP Generates Transaction
▼
2. Check if REFERENCE exists in Tally
▼
3a. If exists → Skip Push
OR
3b. If not exists → Push with REFERENCE
▼
4. Log Result for Future Reference
;; Step 1: Before pushing, check if reference exists
<ENVELOPE>
  <HEADER><TALLYREQUEST>Export Data</TALLYREQUEST></HEADER>
  <BODY><EXPORTDATA><REQUESTDESC>
    <REPORTNAME>Voucher Register</REPORTNAME>
    <STATICVARIABLES>
      <SVCURRENTCOMPANY>ABC Ltd.</SVCURRENTCOMPANY>
      <REFERENCE>ERP-INV-98765</REFERENCE>
    </STATICVARIABLES>
  </REQUESTDESC></EXPORTDATA></BODY>
</ENVELOPE>

;; If response contains the voucher → skip push
;; If response is empty → safe to push
💡 Idempotency Best Practice:
• Always set a unique <REFERENCE> field in every voucher
• Use your ERP's transaction ID (e.g., INV-98765) as the reference
• Before push, query Tally for that reference
• If found, skip the push and log as "already synced"
• Store the Tally voucher number in your ERP after successful push

Retry Logic

  • Retry on network errors only — do not retry on validation errors (they will fail again)
  • Exponential backoff: 1s, 2s, 4s, 8s, 16s between retries
  • Max retries: 3-5 attempts, then move to dead letter queue
  • Log every attempt with timestamp, response, and error
  • Alert on repeated failures — do not silently drop transactions

Duplicate Detection Scenarios

Scenario 1: Network Timeout After Successful Save

Your integration sends a voucher, Tally saves it successfully, but the HTTP response times out. Your code thinks it failed and retries. Without idempotency, the voucher is created twice. With idempotency (checking REFERENCE first), the retry detects the existing voucher and skips the push.

Scenario 2: User Clicks Sync Twice

A user clicks the "Sync to Tally" button twice in quick succession. Without idempotency, two identical vouchers are created. With idempotency, the second sync finds the reference already exists and skips.

Scenario 3: Batch Job Re-runs

A nightly batch job re-runs due to a scheduler glitch. Without idempotency, every transaction is duplicated. With idempotency, the second run finds all references already synced and skips everything.

🚨 The Silent Killer: Duplicate vouchers do not throw errors. They sit in your books silently, corrupting your financial statements. The only way to detect them is manual reconciliation — which is expensive and error-prone. Idempotency is not optional. It is mandatory.
12

Security for XML Integration

Protecting your Tally data when exposed via HTTP

Tally's XML server has no built-in authentication. Anyone who can reach port 9000 can read and write your accounting data. This is the single biggest security risk in Tally integrations.

The 7 Security Controls You Must Implement

  1. Firewall Restriction: Only allow port 9000 from known IP addresses (your integration server)
  2. VPN or Private Network: Place Tally and your integration service on the same private network or VPN
  3. Reverse Proxy: Put Nginx or IIS in front of Tally with HTTPS, authentication, and rate limiting
  4. API Key Authentication: Require a secret API key in every request header (validated by the proxy)
  5. Rate Limiting: Limit requests per second/minute to prevent abuse
  6. Audit Logging: Log every request, source IP, and response on the proxy
  7. Least Privilege: Run Tally under a restricted Windows user account

Network Security Architecture

Integration Service (Cloud / On-Prem)
HTTPS · API Key · Rate Limited
▼
Reverse Proxy (Nginx/IIS) — Auth + TLS
HTTP · Localhost Only
▼
TallyPrime · Port 9000 (Bound to Localhost)
🚨 Never Do This:
• Never expose port 9000 directly to the internet
• Never use default Windows firewall settings that allow Public network access
• Never send credentials in plain HTTP
• Never let multiple external systems talk directly to Tally
• Never disable Windows Firewall "for convenience"

Windows Firewall — Restrict to Specific IPs

# PowerShell — Restrict Tally port 9000 to specific IP
New-NetFirewallRule -DisplayName "Tally XML - Integration Server Only" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 9000 `
    -RemoteAddress "192.168.1.50" `
    -Action Allow `
    -Profile Domain,Private

Nginx Reverse Proxy Example

# /etc/nginx/sites-enabled/tally-proxy
server {
    listen 443 ssl http2;
    server_name tally-api.yourdomain.com;

    ssl_certificate /etc/ssl/certs/tally.crt;
    ssl_certificate_key /etc/ssl/private/tally.key;

    # Rate limiting
    limit_req zone=api burst=10 nodelay;

    location / {
        # API key check
        if ($http_x_api_key != "your-secret-key-here") {
            return 401;
        }

        # Forward to Tally on localhost
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header Host $host;
        proxy_read_timeout 120s;
        client_max_body_size 20M;
    }
}
💡 Enterprise Pattern: In a large enterprise, place Tally behind an integration middleware (like Mulesoft, Boomi, or a custom ASP.NET service). External systems talk to the middleware, not to Tally directly. The middleware handles auth, logging, transformation, and retry.
13

Building a Production Integration Service

Complete ASP.NET Core service with queueing, retry, idempotency, and logging

Let us bring everything together into a production-ready integration service. This is the pattern used by real businesses to reliably sync thousands of transactions per day.

Service Architecture

Custom ERP / Web App
REST API Call
▼
ASP.NET Core Integration Service
Queue · Log · Validate · Transform
▼
TallyPrime XML Server (Port 9000)
Retry on Failure · Idempotency Check
▼
Success → Log · Failure → Dead Letter Queue

Core C# Service — XML Client

using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

public class TallyXmlClient
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;

    public TallyXmlClient(HttpClient httpClient, IConfiguration config)
    {
        _httpClient = httpClient;
        _baseUrl = config["Tally:BaseUrl"] ?? "http://localhost:9000";
        _httpClient.Timeout = TimeSpan.FromSeconds(180);
    }

    public async Task<string> PostAsync(string xmlPayload)
    {
        var content = new StringContent(
            xmlPayload,
            Encoding.UTF8,
            "text/xml");

        var response = await _httpClient.PostAsync(_baseUrl, content);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

Envelope Builder

public static class TallyEnvelope
{
    public static string BuildImport(string company, string innerXml)
    {
        return $@"<ENVELOPE>
    <HEADER>
        <TALLYREQUEST>Import Data</TALLYREQUEST>
    </HEADER>
    <BODY>
        <IMPORTDATA>
            <REQUESTDESC>
                <REPORTNAME>Vouchers</REPORTNAME>
                <STATICVARIABLES>
                    <SVCURRENTCOMPANY>{company}</SVCURRENTCOMPANY>
                </STATICVARIABLES>
            </REQUESTDESC>
            <REQUESTDATA>
                <TALLYMESSAGE>{innerXml}</TALLYMESSAGE>
            </REQUESTDATA>
        </IMPORTDATA>
    </BODY>
</ENVELOPE>";
    }
}

Idempotency Check

public async Task<bool> VoucherExistsAsync(string company, string reference)
{
    var envelope = $@"<ENVELOPE>
    <HEADER><TALLYREQUEST>Export Data</TALLYREQUEST></HEADER>
    <BODY><EXPORTDATA><REQUESTDESC>
        <REPORTNAME>Voucher Register</REPORTNAME>
        <STATICVARIABLES>
            <SVCURRENTCOMPANY>{company}</SVCURRENTCOMPANY>
            <REFERENCE>{reference}</REFERENCE>
        </STATICVARIABLES>
    </REQUESTDESC></EXPORTDATA></BODY>
</ENVELOPE>";

    var response = await _client.PostAsync(envelope);

    // If response contains a VOUCHER element with matching REFERENCE,
    // the voucher already exists.
    return response.Contains($"<REFERENCE>{reference}</REFERENCE>");
}

Retry with Exponential Backoff

public async Task<TallyResult> PushWithRetryAsync(
    string xmlPayload,
    int maxRetries = 5)
{
    for (int attempt = 0; attempt < maxRetries; attempt++)
    {
        try
        {
            var response = await _client.PostAsync(xmlPayload);

            // Check for Tally-level errors in the response
            if (response.Contains("<ERRORS>0</ERRORS>"))
                return TallyResult.Success(response);

            // Tally returned a validation error — do not retry
            return TallyResult.ValidationError(response);
        }
        catch (HttpRequestException)
        {
            // Network error — retry with backoff
            var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
            await Task.Delay(delay);
        }
        catch (TaskCanceledException)
        {
            // Timeout — retry with backoff
            var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
            await Task.Delay(delay);
        }
    }

    return TallyResult.Failed("Max retries exceeded");
}

Complete Push Flow

public async Task<TallyResult> PushSalesVoucherAsync(
    SalesVoucherDto voucher)
{
    // Step 1: Check idempotency
    if (await VoucherExistsAsync(
        company: "ABC Ltd.",
        reference: voucher.ErpId))
    {
        return TallyResult.AlreadyExists(voucher.ErpId);
    }

    // Step 2: Build XML
    var voucherXml = SalesVoucherBuilder.Build(voucher);
    var envelope = TallyEnvelope.BuildImport(
        company: "ABC Ltd.",
        innerXml: voucherXml);

    // Step 3: Push with retry
    var result = await PushWithRetryAsync(envelope);

    // Step 4: Log outcome
    await _logger.LogAsync(new IntegrationLog
    {
        ErpId = voucher.ErpId,
        Direction = "Push",
        Status = result.IsSuccess ? "Success" : "Failed",
        ErrorMessage = result.ErrorMessage,
        Timestamp = DateTime.UtcNow,
        Response = result.RawResponse
    });

    return result;
}

Production Checklist

✓ Production Requirements
  • Idempotency check before every push
  • Exponential backoff retry on network errors
  • Dead letter queue for repeated failures
  • Structured logging (JSON logs)
  • Correlation IDs across ERP and Tally
  • Health check endpoint
  • Metrics dashboard (success rate, latency)
  • Alerting on failure rate spikes
  • Config externalised (URLs, companies, timeouts)
  • Deployment behind reverse proxy
✗ Production Anti-Patterns
  • Retrying on validation errors
  • Ignoring the XML response body
  • Hardcoded company names
  • No timeout on HTTP client
  • Logging to flat text files
  • Running without idempotency
  • Pushing vouchers without masters
  • No monitoring or alerting
  • Exposing Tally port publicly
  • Not testing with real data volumes
💰 Business Impact of a Good Integration Service:
• Time saved: 40+ hours/month of manual data entry eliminated
• Error rate: From ~5% manual entry errors to 0% automated
• Speed: Orders sync to Tally in seconds, not hours
• Compliance: Every transaction logged, traceable, audit-ready
• Cost: One-time development cost vs. ongoing manual effort
🚀
Real-World Scale: A Bangladeshi FMCG distributor using this exact pattern syncs 15,000+ sales invoices per day from their field-force mobile app to TallyPrime — with zero duplicate vouchers and 99.8% uptime over 3 years.
14

Frequently Asked Questions

Answers to the most common XML integration questions
❓
Can I send multiple vouchers in a single XML request?
▾

Yes. Wrap each voucher in its own <VOUCHER> element inside the same <TALLYMESSAGE>. Tally processes them sequentially. Recommended batch size: 100-500 vouchers per request. Larger batches risk timeouts.

❓
What happens if a voucher in a batch fails?
▾

Tally processes each voucher independently. If voucher 3 fails, vouchers 1, 2, 4, 5 are still created. Tally's response includes a per-voucher error report. Your integration service must parse this and handle partial success. Do not assume batch success if one voucher succeeds.

❓
How do I handle a voucher that references a non-existent ledger?
▾

Two options:

  • Pre-create ledgers: Include the LEDGER element in the same TALLYMESSAGE before the VOUCHER
  • Auto-create on the fly: Use TDL to auto-create ledgers when a missing ledger is referenced (requires TDL customization)

Best practice: pre-sync masters separately, then push transactions.

❓
Why does my XML request time out for large payloads?
▾

Tally is single-threaded. Large payloads (10+ MB, 1000+ vouchers) take time to process. Solutions:

  • Reduce batch size to 100-500 vouchers
  • Increase HTTP client timeout to 180+ seconds
  • Use sequential processing, not parallel
  • Split imports by voucher type
❓
Can Tally push data to my system, or only receive?
▾

Tally cannot push data by itself. It has no outbound webhooks. Your integration service must poll Tally using Export requests, or use TDL Actions with HTTP POST to push data when certain events fire (e.g., voucher save). The TDL approach is more complex but enables real-time push.

❓
Can I delete a voucher via XML?
▾

Yes. Set ACTION="Delete" on the voucher. You need the exact voucher details (date, number, type) to target the correct voucher. Use this carefully — deletion breaks audit trail integrity. In most cases, prefer reversal (Credit Note / Debit Note) over deletion.

❓
Does XML integration work with TallyPrime Server?
▾

Yes. TallyPrime Server supports XML integration on the same port 9000. The advantage is higher concurrency handling and better performance under load. Client-server setup still uses the same XML format.

❓
Can I use JSON instead of XML for integration?
▾

Yes. TallyPrime supports JSON over HTTP as an alternative to XML. JSON is more compact and easier to parse in modern languages. However, XML is more mature and has more examples, documentation, and community support. For complex vouchers with inventory and bill allocations, XML is still the safer choice.

❓
How do I know if my push succeeded?
▾

Parse the XML response and check these elements:

  • <CREATED>1</CREATED> — voucher created
  • <ALTERED>1</ALTERED> — voucher altered
  • <ERRORS>0</ERRORS> — no errors
  • <LINEERROR></LINEERROR> — empty means no error

If any of these indicate failure, log the full response for debugging.

❓
What is the maximum voucher number length?
▾

Tally allows voucher numbers up to 16 characters. If your ERP transaction IDs are longer (e.g., GUIDs), use a shorter prefix + sequential number, and store the full ERP ID in the <REFERENCE> field instead.

Learning Path for XML Integration

  1. Day 1-2: Enable HTTP server, test with curl/Postman
  2. Day 3-5: Master the envelope structure
  3. Day 6-10: Create ledgers, stock items, units via XML
  4. Day 11-15: Create vouchers — Sales, Purchase, Receipt, Payment
  5. Day 16-20: Read data via Export requests
  6. Day 21-25: Implement idempotency and retry logic
  7. Day 26-30: Build production integration service with logging and monitoring
💡 Final Tip: The fastest way to master XML integration is to turn on verbose logging and inspect every request and response. Print the raw XML. Read the error messages. Try small changes. Iterate. In a week of focused practice, you will be more capable than 90% of Tally developers.

Series Roadmap — 45 Parts to CFO-Level Mastery

You have completed 23 parts of the TallyPrime Deep-Dive Masterclass. Here is the complete roadmap.

01 Foundation
02 History
03 vs ERP
04 Company
05 COA
06 Double Entry
07 Vouchers
08 Sales
09 Purchase
10 Inventory
11 Inv+Acct
12 AR/AP
13 Banking
14 Tax
15 Cost Centre
16 Payroll
17 Manufacturing
18 Financial Stmt
19 MIS
20 CFO
21 Security
22 Architecture
23 TDL
24 XML 📡
25 JSON/ODBC
26 Custom ERP
27 Tally Engine
28 Enterprise
29 Tally+AI+BI
30 Case Study
31-45 Advanced
🎯 What's Next: Part 25 — JSON & ODBC Integration explores Tally's modern interfaces — JSON over HTTP for REST-friendly applications and ODBC for analytics, Power BI, and Excel. Then Parts 26-30 cover custom ERP integration, enterprise architecture, AI, and the grand case study.

Post a Comment

0 Comments