← All Posts

Simplifying Iranian Taxpayer System Integration: Building a Secure NestJS Package

13 min read Copy URL

Integrating with government APIs—especially financial ones like the Iranian Taxpayer System (سامانه مودیان)—is notoriously difficult. It involves complex cryptography, strict validation rules, and specific protocol requirements that few developers are prepared to handle. After spending weeks wrestling with JWS/JWE standards, documentation gaps, and edge cases, I built the iranian-taxpayer-system npm package to solve this problem once and for all.

The Problem: Why Government APIs Are Hard

The Iranian Taxpayer System is designed to collect electronic invoices, standardize tax procedures, and reduce errors in tax reporting. It's a critical infrastructure for Iranian businesses. But from a developer's perspective, it's a nightmare.

Unlike typical REST APIs, this system requires:

  • Multi-layered authentication: Challenge-response mechanisms using dynamic nonces
  • Digital signatures (JWS): Every packet must be signed using the taxpayer's private key with RS256 algorithm
  • Encryption (JWE): The signed packet must then be encrypted using the Tax Authority's public key
  • Strict data formatting: Specific JSON structures, mandatory fields, and mathematical validation
  • Precise timestamps: Strict UTC formatting to prevent server rejection

Most developers would attempt to implement this from scratch, leading to security vulnerabilities, failed submissions, and countless hours of debugging. I decided to build a package that abstracts away these complexities.

🎧 Listen to the Documentation Overview

Prefer listening? I've created an audio walkthrough of the complete documentation:

How It Works: The Core Architecture

The iranian-taxpayer-system package is built on three pillars: security, validation, and orchestration. Let me walk you through each component.

1. The Cryptography Layer (CryptoService)

The most critical component is secure handling of JWS/JWE protocols. This is where the magic—and the complexity—lives.

Digital Signatures (JWS): The service uses the JOSE library to sign payloads with RS256 (RSASSA-PKCS1-v1_5). It automatically injects the required headers:

  • x5c – The X.509 certificate chain
  • sigT – Signature timestamp in UTC format
  • crit – Critical headers flag

Encryption (JWE): After signing, the payload is encrypted using:

  • Key Encryption: RSA-OAEP-256 (encrypts the symmetric key)
  • Content Encryption: AES-256-GCM (encrypts the actual data)

The package handles all of this automatically—just provide your private key and certificate, and it's handled behind the scenes.

2. The Validation Engine (InvoiceValidatorService)

Instead of sending data and hoping it works, the package validates everything locally first. This prevents wasted API calls and cryptic error responses from the government server.

The validator checks:

  • Mathematical accuracy: Verifies that (Quantity × Price) - Discount + VAT equals the total (down to 1 Rial precision)
  • Pattern-specific rules: Different invoice types have different requirements. A "Gold Seller" invoice needs purity/weight, while an "Export" invoice needs customs license numbers.
  • Data integrity: Validates 22-character Tax IDs, Economic Codes, valid dates, and mandatory field presence
  • Format compliance: Ensures string lengths, field types, and allowable values match the RC_IITP.IS_V7.6 specification

3. The Orchestration Layer (TaxpayerService)

This is the "brain" that ties everything together and manages the full lifecycle of invoice submission.

  1. Authentication: Requests a nonce from the server, signs it to generate an Auth Token, and manages token lifecycle
  2. Validation: Runs the invoice through the validator before proceeding
  3. Packaging: Takes validated invoice data, passes it through the crypto layer, and formats it for submission
  4. Submission: Sends the secure packet to the API and retrieves a unique Reference Number for tracking
  5. Inquiry: Provides methods to check invoice status by Reference ID, UID, or Tax ID
  6. Logging: Includes a custom HTTP interceptor that logs request durations, status codes, and errors for debugging

Key Features That Matter

Here's why developers should use this package instead of building it themselves:

🔒 Developer Experience (DX)

The package is a drop-in NestJS module. Just configure it once with your private key and certificate, and the cryptographic handshake is handled automatically.

🎯 Type Safety

Built entirely in TypeScript with full type definitions (InvoicePayload, InvoiceHeader, InvoiceBody, etc.). Developers get IDE autocomplete and catch errors at compile time, not during testing.

🛡️ Failsafe Validation

The built-in validator acts as a safety net, preventing "Bad Request" errors by catching logical and format mistakes before they leave your server. This is invaluable when dealing with government systems.

📊 Support for All Invoice Patterns

The package supports specialized invoice patterns:

  • Pattern 1: Sales (فروش)
  • Gold & Jewelry Sales
  • Contracting Services
  • Exports
  • Currency Sales
  • And more...

🔍 Full Invoice Lifecycle Management

Beyond submission, the package provides inquiry methods to:

  • Check invoice status by Reference ID
  • Look up invoices by UID (internal trace ID)
  • Verify taxpayer fiscal status
  • Check Article 6 limits

How Clean Is the API?

Here's the beautiful part—despite all the complexity it hides, using the package is remarkably simple:

// 1. Inject the service
constructor(private readonly taxpayerService: TaxpayerService) {}

// 2. Create your invoice object
const invoice: InvoicePayload = {
  header: {
    taxid: '1234567890123456789012',  // 22-character Tax ID
    inty: InvoiceType.TYPE_1,          // Invoice type
    inp: InvoicePattern.SALES,         // Sales pattern
    indatim: new Date(),               // Date/time
    // ... other fields
  },
  body: [{
    sstid: 'GOODS_SERVICE_ID',         // Goods/service ID
    am: 1,                             // Amount/quantity
    fee: 1000000,                      // Unit price
    // ... item details
  }],
};

// 3. Submit!
// The library handles validation, signing, encryption, and token management automatically.
const result = await this.taxpayerService.submitInvoice(invoice);
console.log(`Success! Reference ID: ${result.result[0].referenceNumber}`);
        

All the JWS/JWE complexity, nonce fetching, validation, and authentication happen behind the scenes. Your code remains clean and focused on business logic.

Technical Deep Dive

For the hardcore developers who want to understand what's happening under the hood:

The Authentication Handshake

Before submitting invoices, the package performs a mandatory authentication flow:

  1. Get Nonce: Calls GET /nonce to retrieve a random, one-time-use challenge string
  2. Sign the Token: Creates a JWS using the nonce + clientId (Fiscal Memory ID), signed with the user's private key
  3. Exchange for Auth Token: Sends the signed token to get a Bearer Token for subsequent calls

The Invoice Submission Pipeline

This is the core of the package's functionality:

  1. Data Structuring: Maps invoice data to the strict JSON schema (Header, Body, Payments)
  2. Pre-validation: Runs local validation checks before cryptography
  3. JWS Signing: Signs the payload with RS256, ensuring integrity
  4. JWE Encryption: Encrypts the signed payload using RSA-OAEP-256 and A256GCM
  5. Submission: Sends to POST /invoice, receiving a unique Reference Number

Key Formats and Specifications

The package expects:

  • Private Key: PKCS#8 format (PEM-encoded)
  • Certificate: Standard X.509 format (.crt file)
  • Timestamps: Strict UTC format: yyyy-MM-dd'T'HH:mm:ss'Z'
  • Algorithms: Signing (RS256), Key Encryption (RSA-OAEP-256), Content Encryption (A256GCM)

Getting Started

The package is published on npm and easy to install:

npm install iranian-taxpayer-system

Then configure it in your NestJS application:

// app.module.ts
import { Module } from '@nestjs/common';
import { TaxpayerModule } from 'iranian-taxpayer-system';

@Module({
  imports: [
    TaxpayerModule.forRoot({
      privateKey: process.env.TAX_PRIVATE_KEY,
      certificate: process.env.TAX_CERTIFICATE,
      clientId: process.env.TAX_CLIENT_ID,
      baseUrl: 'https://tp.tax.gov.ir',
    }),
  ],
})
export class AppModule {}
        

For more details, check out the GitHub repository and npm package page, and Dockerize Java Service

Conclusion

Building a bridge between Iranian businesses and the government's tax system is no trivial task. The technical barriers—cryptography, validation, API quirks—are real and significant.

By open-sourcing this package, I'm hoping to democratize access to this critical infrastructure. Whether you're building a simple POS system or a complex ERP, your developers shouldn't have to spend weeks wrestling with JWE encryption and government API documentation. They should focus on business logic.

If you're building tax compliance software in Iran, find me via contact link. And if you can work with Java and microservices, you can use the Java Version in the Government Website 1, or the Dockerize version in my github 2.

NestJS npm Package Cryptography Government APIs TypeScript Tax Compliance Open Source
* * *

References and Additional Resources

  1. Store terminals and payment system
  2. Iranian Tax System SDK for Java 🇮🇷 (Dockerize and Ready to use)
  3. Iran's Tax Affairs Organization
  4. INTA E-Invoice API Implementation (PDF) - Presentation Style Document.
☀️ 🌙