← Back to Documentation
ERPNext logo

ERPNext Integration with UAE FTA E-Invoicing via TrustBill

Connect ERPNext to TrustBill for PINT-AE compliant e-invoicing · Frappe v14 / v15 / v16+ · API + Webhook + CSV

Overview

This guide explains how to connect ERPNext — the open-source Frappe ERP used by manufacturers, distributors and service companies across the UAE — to TrustBill, a UAE FTA-accredited service provider (ASP). The result is fully PINT-AE compliant e-invoicing submitted to the FTA without leaving ERPNext.

ERPNext already handles multi-currency, TRN, UAE VAT and bilingual item/customer masters. What it does not include out of the box is PINT-AE XML generation and transmission through an FTA-accredited ASP over the Peppol network. TrustBill closes that gap.

What this integration delivers

  • Submit Sales Invoices and Credit Notes directly from ERPNext to the FTA
  • Frappe Server Script webhook — no core-file changes, survives version upgrades
  • PINT-AE XML generated, validated and delivered by TrustBill on your behalf
  • FTA acknowledgement written back to ERPNext custom fields on the Sales Invoice
  • Optional custom Frappe app for packaged install and version-managed updates
  • Bulk migration path for historical invoices via CSV or API replay

What is ERPNext?

ERPNext is an open-source, modular ERP built on the Frappe framework. It is deployed either as Frappe Cloud (managed SaaS), Frappe Cloud Private, or self-hosted on-premise / VPS using the Frappe Bench stack.

  • Core DocTypes: Sales Invoice, Customer, Item, Company, Address, Tax Template, Payment Terms Template
  • Native REST API: /api/resource/{DocType} and /api/method/{method.path}
  • Server Scripts: attach Python hooks to document events without modifying core code
  • Scheduled Jobs: cron-style background workers inside Frappe's job queue
  • Multi-currency, multi-company, multi-branch and GCC VAT ready

Supported Versions

ERPNext VersionFrappe VersionSupport Level
v14.xv14.xFully supported
v15.xv15.xRecommended + fully supported
v16+ / developv16+Supported with minor schema checks

Prerequisites

  • ERPNext site running v14, v15 or v16 with Server Script permissions enabled
  • UAE company set with TRN in Company master
  • Customer TRNs populated for B2B clients (simplified invoices can omit customer TRN)
  • Active TrustBill SME account with API credentials
  • Network egress allowed from ERPNext host to TrustBill API

Integration Architecture

The standard flow keeps ERPNext as the system of record for invoices and pushes a PINT-AE-ready payload to TrustBill at submission time.

  1. Invoice created / submitted in ERPNext Sales Invoice DocType.
  2. Frappe Server Script (After Submit event) transforms the invoice into TrustBill's JSON format.
  3. POST to TrustBill /api/v1/invoices with API key authentication.
  4. TrustBill validates the payload, converts to PINT-AE XML and delivers over Peppol via the FTA accredited ASP.
  5. FTA acknowledgement is stored back in ERPNext custom fields on the same invoice.

Connection Methods

1. Server Script + Webhook (Recommended)

Use Frappe Server Script on the Sales Invoice on_submit hook. No custom app to install, upgrades-safe, and works on Frappe Cloud and self-hosted sites.

2. Custom Frappe App

Package the integration as a TrustBill Frappe app for multi-site deployments, version control and automated updates via bench update.

3. Scheduled Batch Sync

For high-volume sites, run a Scheduled Job that polls submitted invoices and pushes them to TrustBill in batches every 2 minutes.

4. CSV Export / Import

Fallback for one-off migrations: export Sales Invoice Register from ERPNext, transform columns, and upload via TrustBill bulk import.

Authentication

TrustBill API uses HTTP Bearer tokens. Generate an API key from TrustBill Settings → API Keys, then include it in every request header.

POST https://trustbill.ae/api/v1/invoices
Authorization: Bearer tb_your_api_key_here
Content-Type: application/json

For Frappe outbound calls, store the token in a TrustBill Settings single DocType or in the site config and read it via frappe.conf.get().

Server Script Setup

This is the fastest production-ready setup. It sends every submitted Sales Invoice to TrustBill automatically.

Step 1: Add custom fields to Sales Invoice

  • trustbill_status (Select): Pending / Submitted / Acknowledged / Rejected
  • trustbill_uuid (Data): TrustBill invoice UUID
  • trustbill_ack_ref (Data): FTA acknowledgement reference
  • trustbill_reject_reason (Text): Error / rejection message

Step 2: Create Server Script (After Submit)

import json
import requests
import frappe

def submit_to_trustbill(doc, method):
    if doc.trustbill_status and doc.trustbill_status != "Pending":
        return

    token = frappe.conf.get("trustbill_api_token")
    url = "https://trustbill.ae/api/v1/invoices"

    payload = {
        "invoiceNumber": doc.name,
        "issueDate": str(doc.posting_date),
        "dueDate": str(doc.due_date) if doc.due_date else str(doc.posting_date),
        "currency": doc.currency,
        "seller": {
            "trn": frappe.get_value("Company", doc.company, "tax_id"),
            "legalName": doc.company,
            "address": get_company_address(doc.company)
        },
        "buyer": {
            "trn": doc.tax_id or "",
            "legalName": doc.customer_name,
            "address": get_customer_address(doc.customer)
        },
        "items": [
            {
                "name": item.item_name,
                "quantity": float(item.qty),
                "unitPrice": float(item.rate),
                "discount": float(item.discount_amount or 0),
                "taxPercent": float(item.tax_rate or 0),
                "lineTotal": float(item.amount)
            }
            for item in doc.items
        ],
        "totals": {
            "netAmount": float(doc.net_total),
            "taxAmount": float(doc.total_taxes_and_charges or 0),
            "grossAmount": float(doc.grand_total)
        }
    }

    try:
        resp = requests.post(
            url,
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json"
            },
            json=payload,
            timeout=30
        )
        data = resp.json()
        if resp.ok:
            doc.db_set("trustbill_status", "Submitted", commit=False)
            doc.db_set("trustbill_uuid", data.get("uuid"), commit=False)
        else:
            doc.db_set("trustbill_status", "Rejected", commit=False)
            doc.db_set("trustbill_reject_reason", json.dumps(data), commit=False)
    except Exception as e:
        doc.db_set("trustbill_status", "Rejected", commit=False)
        doc.db_set("trustbill_reject_reason", str(e), commit=False)

def get_company_address(company):
    addr = frappe.get_all("Address", filters={"is_your_company_address": 1, "link_name": company}, limit=1)
    if addr:
        return frappe.get_value("Address", addr[0].name, "address_line1")
    return ""

def get_customer_address(customer):
    addr = frappe.get_all("Dynamic Link", filters={"parenttype": "Address", "link_doctype": "Customer", "link_name": customer}, limit=1)
    if addr:
        return frappe.get_value("Address", addr[0].parent, "address_line1")
    return ""

Step 3: Wire the event

Create a new Server Script: DocType Event → Sales Invoice → After Submit → Paste submit_to_trustbill(doc, method). Enable and save.

ERPNext → TrustBill Field Mapping

ERPNext FieldTrustBill JSONNotes
doc.nameinvoiceNumberMust be unique per TRN
posting_dateissueDateISO-8601 date
Company.tax_idseller.trn15-digit UAE TRN
Customer.tax_idbuyer.trnRequired for B2B
items[].item_nameitems[].nameArabic + English if available
items[].qtyitems[].quantityNumeric, positive
items[].rateitems[].unitPriceAfter line discount
grand_totaltotals.grossAmountVAT inclusive

UAE VAT / Tax Mapping

Map ERPNext Sales Taxes and Charges Template rows to FTA tax categories. TrustBill accepts the following UAE tax category codes.

ERPNext Tax RowTrustBill taxCategoryRate
Standard VATSTANDARD5%
Zero RatedZERO_RATE0%
ExemptEXEMPT
Reverse ChargeREVERSE_CHARGE5%
Out of ScopeOUT_OF_SCOPE

Credit Notes

ERPNext Sales Return / Credit Note should be mapped to TrustBill's credit-note endpoint. Include the original invoice number in originalInvoiceNumber and a negative gross amount.

POST https://trustbill.ae/api/v1/credit-notes
{
  "originalInvoiceNumber": "SINV-00001",
  "issueDate": "2026-09-08",
  "creditNoteNumber": "CN-00001",
  "reason": "Goods returned",
  "totals": {
    "grossAmount": -525.00
  }
}

Status Tracking

TrustBill can call a webhook back to your Frappe site whenever an invoice status changes. Add a custom API method in ERPNext to receive the callback.

# In a Server Script marked as API method
def handle_trustbill_webhook():
    import frappe, json
    data = frappe._dict(frappe.request.get_json())
    frappe.db.set_value(
        "Sales Invoice",
        data.invoiceNumber,
        {
            "trustbill_status": data.status,
            "trustbill_ack_ref": data.ackRef or None,
            "trustbill_reject_reason": data.reason or None
        }
    )
    return {"ok": True}

Register the webhook URL in TrustBill Settings → Webhooks:/api/method/handle_trustbill_webhook.

Bulk Migration

For historical invoices, use a one-off Frappe Background Job or the Desk "Bulk Update" tool to replay submitted invoices through the TrustBill API.

def replay_invoices():
    for name in frappe.get_all("Sales Invoice", filters={"docstatus": 1}, pluck="name"):
        doc = frappe.get_doc("Sales Invoice", name)
        submit_to_trustbill(doc, "on_submit")
        frappe.db.commit()

Sandbox & Live Modes

TrustBill provides a sandbox endpoint for end-to-end testing without touching the FTA. Use it until your FTA registration and ASP assignment are complete.

ModeEndpointWhat it does
Sandboxhttps://trustbill.ae/api/v1/invoices?mode=sandboxValidates PINT-AE XML, no FTA delivery
Livehttps://trustbill.ae/api/v1/invoices?mode=liveSubmits to FTA via ASP over Peppol

Multi-Company & Multi-Currency

Frappe/ERPNext supports multiple Company doctypes in one site — common for UAE groups running a mainland trading company alongside a free-zone company. TrustBill treats each ERPNext company as an independent seller with its own TRN.

Company → TrustBill profile mapping

Add a custom field custom_trustbill_api_key on the Company doctype, or maintain a lookup in your Server Script, so each company's Sales Invoices resolve to the correct TrustBill API key and TRN. Never submit two companies' invoices under one shared TRN.

Multi-currency invoices

ERPNext's Sales Invoice stores currency and conversion_rate against the company's base currency. When the invoice currency is not AED, send both the trade currency amount and the AED-converted total (using base_grand_total) so the PINT-AE document always carries an AED value for FTA reporting.

Multiple branches / cost centers

If ERPNext branches map to different Emirates or physical locations, include the branch/cost center code in your internal audit log even though PINT-AE itself doesn't require a branch field — it helps reconcile FTA submissions during a VAT audit.

Error Handling & API Errors

Wrap every TrustBill call in your Server Script with proper error handling — log the response and update the Sales Invoice status field rather than letting the script fail silently.

HTTP StatusMeaningAction
400Malformed JSON or missing required fieldFix the Server Script payload; do not retry unchanged
401Invalid or expired API keyRotate the key in ERPNext's site config / secrets
403TRN on invoice does not match the API key's registered TRNFix company → TRN mapping
409Duplicate invoice number for this sellerCheck idempotency key / Sales Invoice name reuse
422PINT-AE validation failure (tax, TRN format, totals mismatch)Inspect errors[] array; fix mapping
429 / 5xxRate limited or TrustBill/FTA temporarily unavailableRetry with exponential backoff (max ~5 attempts)

Setup Checklist

  • UAE TRN configured for every ERPNext company
  • Customer records populated with TRNs for B2B customers
  • Server Script / REST API access enabled and API secret rotated out of source control
  • Company → TrustBill API key mapping created (custom field or config)
  • Tax template → PINT-AE tax category mapping reviewed with finance
  • Credit Note doctype flow mapped to TrustBill credit notes
  • Webhook or polling job configured for status write-back
  • Sandbox invoices tested for standard, zero-rated, exempt and reverse-charge cases
  • Bulk migration CSV template validated against historical Sales Invoice data
  • Go-live date agreed and live mode switched only after sandbox sign-off

Troubleshooting

  • 422 validation error: Check that all B2B customer TRNs are 15 digits and company TRN is set.
  • Server Script not firing: Confirm the script is enabled and the DocType Event is "After Submit".
  • Timeout: Increase Frappe HTTP timeout to 60s or switch to a background job for high-latency links.
  • Missing Arabic names: Populate the "Arabic Name" custom field on Customer and Item; include it in the payload.

Support

Need help with the ERPNext integration? Our integrations team can review your Server Script, field mapping and VAT setup.