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.
XML Integration Overview
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.
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
| Interface | Read | Write | Maturity | Best 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
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
- 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
- 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
The XML Envelope — Anatomy & Structure
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
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
| Element | Purpose | Required |
|---|---|---|
<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 |
<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 reportSVEXPORTFORMAT— Export format (XML, CSV, etc.)SVSHOWLEDGERDETAILS— Show detailed ledger entriesSVINVENTORYDETAILS— Include inventory detailsSVCostCentre— Filter by cost centre
20260918) in STATICVARIABLES. In voucher payloads, you can use YYYYMMDD or DD-MMM-YYYY. Consistency matters — pick one format and stick with it.
HTTP Setup & Configuration
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
- Open TallyPrime with a company loaded
- Press F1 → Settings → Connectivity
- Under Tally.NET Server, set the following:
- Tally.NET Server: Both (Client and Server)
- Port: 9000 (default; change if there is a conflict)
- Press Ctrl+A to save
- Restart TallyPrime for the settings to take effect
Step 2 — Configure Windows Firewall
If your integration service runs on a different machine than TallyPrime, you must allow incoming traffic on port 9000.
- 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
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
| Issue | Symptom | Fix |
|---|---|---|
| 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 |
<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)
Request Types — Import, Export, Execute
The <TALLYREQUEST> element tells Tally what kind of operation you are performing. There are three main types:
1. Import Data — Write to Tally
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
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
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 Type | Purpose | Read/Write | Payload |
|---|---|---|---|
| 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 |
Creating Ledgers via XML
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
| Attribute | Purpose | Values |
|---|---|---|
<NAME> | Ledger name | String |
<PARENT> | Parent group | Sundry Debtors, Sundry Creditors, Bank Accounts, etc. |
<ISBILLWISEON> | Enable bill-wise tracking | Yes / No |
<CREDITLIMIT> | Maximum credit allowed | Number |
<CREDITDAYS> | Credit period in days | Number |
<LEDGSTREGISTRATIONNUMBER> | VAT/GST registration | String |
<ISGSTAPPLICABLE> | GST applicable? | Yes / No |
<ISBANKACCOUNT> | Is bank ledger? | Yes / No |
<BANKACCOUNTNUMBER> | Bank account number | String |
<IFSCODE> | Bank IFSC (India) | String |
<ISCOSTCENTRESON> | Cost centre tracking | Yes / No |
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.
Creating Stock Items, Units & 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
| Attribute | Purpose |
|---|---|
<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 |
Creating Sales Vouchers via XML
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 Entry | ISDEEMEDPOSITIVE | AMOUNT Sign |
|---|---|---|
| Debit (e.g., Customer) | Yes | Positive (+) |
| Credit (e.g., Sales, GST) | No | Negative (−) |
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>
Creating Purchase Vouchers
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
| Entry | Sales Voucher | Purchase Voucher |
|---|---|---|
| Party Ledger | Debit (+) | Credit (−) |
| Sales/Purchase A/c | Credit (−) | Debit (+) |
| Output/Input GST | Credit (−) | Debit (+) |
| Inventory Entry | Credit (−) | Debit (+) |
Creating Receipts, Payments & Contra
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
| BILLTYPE | When to Use |
|---|---|
New Ref | Creating a new invoice/reference (Sales, Purchase invoices) |
Agst Ref | Settling against an existing invoice (Receipts, Payments) |
Advance | Recording an advance paid/received before an invoice |
On Account | Payment that cannot be tied to a specific bill |
<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.
Reading Data — Export Requests
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 Name | What It Returns |
|---|---|
List of Ledgers | All ledgers with their groups |
List of Stock Items | All stock items |
List of Groups | All groups |
List of Companies | All companies loaded in Tally |
Voucher Register | Vouchers for a date range |
Day Book | All vouchers for a day/period |
Outstandings Receivables | Customer-wise outstanding |
Outstandings Payables | Supplier-wise outstanding |
Trial Balance | Ledger balances |
Profit & Loss | P&L report |
Balance Sheet | Balance Sheet |
Stock Summary | Stock item balances |
Error Handling, Idempotency & Retry
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
| Error | Cause | Fix |
|---|---|---|
| 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.
;; 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
• 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
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.
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.
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.
Security for XML Integration
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
- Firewall Restriction: Only allow port 9000 from known IP addresses (your integration server)
- VPN or Private Network: Place Tally and your integration service on the same private network or VPN
- Reverse Proxy: Put Nginx or IIS in front of Tally with HTTPS, authentication, and rate limiting
- API Key Authentication: Require a secret API key in every request header (validated by the proxy)
- Rate Limiting: Limit requests per second/minute to prevent abuse
- Audit Logging: Log every request, source IP, and response on the proxy
- Least Privilege: Run Tally under a restricted Windows user account
Network Security Architecture
• 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; } }
Building a Production Integration Service
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
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
- 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
- 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
• 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
Frequently Asked Questions
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.
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.
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.
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
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.
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.
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.
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.
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.
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
- Day 1-2: Enable HTTP server, test with curl/Postman
- Day 3-5: Master the envelope structure
- Day 6-10: Create ledgers, stock items, units via XML
- Day 11-15: Create vouchers — Sales, Purchase, Receipt, Payment
- Day 16-20: Read data via Export requests
- Day 21-25: Implement idempotency and retry logic
- Day 26-30: Build production integration service with logging and monitoring
Series Roadmap — 45 Parts to CFO-Level Mastery
You have completed 23 parts of the TallyPrime Deep-Dive Masterclass. Here is the complete roadmap.
0 Comments
thanks for your comments!