Engineering Blog / Inside TaxBridge

Solving EFRIS Error 15: The Case of the Unexpected GZIP

Engineering Team
Engineering Team Core Contributors
Published December 11, 2025
Read Time 6 min read

How we debugged and fixed the notorious 'Data decryption error' in the URA EFRIS v20.1 Protocol.

If you’ve spent any time integrating with URA EFRIS v20.1, you know the feeling. Your Handshake (T104) works perfectly. You have your Session Key. You are ready to query products. And then…

{
  "returnCode": "15",
  "returnMessage": "Data decryption error!"
}

“Data decryption error”? But I successfully decrypted the session key! I am using the standard AES-128-ECB as per the spec! What is going on?

The Rabbit Hole

We encountered this recently while rebuilding our EFRIS Client. The handshake (T104) - which uses RSA encryption - was flawless. But every subsequent standard request (like T119 or T127) using the session key failed with Code 15.

We had three main hypotheses:

  1. Double Encoding: Some legacy knowledge suggested URA expects the AES encrypted payload to be the Base64 string of the GZIP data, not the raw GZIP bytes. We tried implementing this “Onion” of AES(Base64(GZIP(JSON))). It failed.
  2. Special Protocol: We suspected T127 (Goods Query) might be a “Plaintext” interface like T124, requiring no session key at all. We tried sending it as RSA-signed plaintext. Result? Code 15 (Server tried to decrypt it anyway).
  3. Key Corruption: Was the session key extracted correctly? We verified the 24-character Base64 string from T104 against independent RSA tools. The key was perfect.

The Breakthrough: Legacy Wisdom

We finally compared our modern implementation against a years-old legacy codebase that was known to work. We traced the encryptJsonContent function line by line.

// Legacy Codebase (Simplified)
protected encryptJsonContent(content, aesKey) {
    const jsonString = JSON.stringify(content);
    // Directly encrypting the JSON string
    return Cryptor.aesEncrypt(jsonString, aesKey); 
}

Do you see what’s missing? GZIP.

Most documentation (and our assumption) implies that all standard payloads should be compressed. Our modern client was setting zipCode: '1' and GZIP-ing everything. The legacy client was strictly sending zipCode: '0' (implied) and encrypting raw JSON.

The Solution

The “Data decryption error” was a red herring. It wasn’t that the encryption was broken—it was that the server decrypted the payload, found unexpected GZIP binary headers instead of a specific format (or failed to auto-detect compression), and threw a generic crypto error.

The fix was simple: Disable Compression for Standard Interfaces.

// StandardEncryptedAdapter.ts

async prepareRequest(...) {
    // 1. Serialize
    const jsonString = JSON.stringify(payload);

    // 2. Compress -> SKIPPED (Set zipCode = '0')
    // const compressed = await gzip(jsonString); 

    // 3. Encrypt Directly
    const encryptedContent = this.aes.encrypt(jsonString, sessionKey);

    return {
        data: {
            content: encryptedContent,
            dataDescription: {
                zipCode: '0', // Crucial!
                encryptCode: '2' // AES
            }
        }
    };
}

Once we removed the GZIP step and updated the headers, T119 and T127 requests started passing immediately.

Key Takeaway

When integrating with government APIs like EFRIS, error codes can be misleading. Code 15 didn’t mean “Wrong Key” or “Bad Math”—it meant “I can’t read what you sent me”. Always verify your assumptions about compression and encoding formats against a working reference implementation if possible.