Skip to content

Payload Structure

The TRP payload follows the TLV (Tag-Length-Value) format with a fixed prefix:

trp01 + [FIELD][LENGTH][VALUE] + ... + [99][04][CRC]

Each field consists of:

  • Tag (2 digits): field identifier
  • Length (2 digits): value size in characters
  • Value (N characters): field content
trp01 0148UQBJ6gU8... 0208store123 0307100000 0403BRL 0508tx123456 9904A3F2
| | | | | | |
| | | | | | +- CRC16-CCITT
| | | | | +- Transaction ID
| | | | +- Currency
| | | +- Amount (cents)
| | +- Merchant ID
| +- TON Wallet (48 chars)
+- TRP v1 Prefix
  1. The payload must start with trp01
  2. Minimum length: 15 characters
  3. Maximum length: 512 characters
  4. Fields can appear in any order, except CRC which is always last
  5. The field length must be numeric and exactly 2 digits

CRC16-CCITT is calculated over the complete payload without the CRC value, but with the field 99 header:

crc_input = payload_without_crc + "9904"
crc = CRC16_CCITT(crc_input) // polynomial 0x1021, initial 0xFFFF

The result is a 4-character uppercase hexadecimal string.

def crc16_ccitt(data: str) -> str:
crc = 0xFFFF
for byte in data.encode('utf-8'):
crc ^= byte << 8
for _ in range(8):
if crc & 0x8000:
crc = (crc << 1) ^ 0x1021
else:
crc = crc << 1
crc &= 0xFFFF
return format(crc, '04X')
function crc16Ccitt(data: string): string {
let crc = 0xffff
for (let i = 0; i < data.length; i++) {
crc ^= data.charCodeAt(i) << 8
for (let j = 0; j < 8; j++) {
crc = crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1
crc &= 0xffff
}
}
return crc.toString(16).toUpperCase().padStart(4, '0')
}