Callbacks

What TrezorPay sends to your callback URL, how it retries, and how to verify the RSA-SHA256 signature.

When callbacks are sent

MomentpendingSent when
Payment seen1Only if you created the address with pending=1
Payment forwarded0Always, once the amount has been sent to your wallet

Each payment to an address gets its own pair of callbacks. Addresses never expire.

Transport

  • GET (default): every field is added to the query string of your callback URL.
  • POST form (post=1): fields in an application/x-www-form-urlencoded body.
  • POST JSON (json=1): fields in a JSON body.

Your own query parameters always stay on the URL.

Fields

FieldDescription
uuidPayment id, identical on its pending and confirmed callback
address_inThe address that was paid
address_outYour wallet, or the split
txid_inCustomer's transaction
txid_outOur forwarding transaction (confirmed only)
confirmationsConfirmations at the time of the callback
value_coinAmount paid
value_forwarded_coinAmount sent to your wallet (confirmed only)
fee_coinOur fee (confirmed only)
coinTicker in underscore form, such as trc20_usdt
priceCoin price in USD
pending1 or 0
resultpending or sent
value_coin_convertFiat values, with convert=1

value, value_forwarded and fee are legacy integer fields: read the _coin strings instead.

Retries

Answer HTTP 200 with the body *ok* within 30 seconds. Anything else is retried after 6, 12, 24 minutes and so on, doubling, until the payment is 3 days old. Every attempt is visible in /{ticker}/logs/.

Verifying the signature

Every callback carries x-ca-signature: a base64 RSA-SHA256 (PKCS#1 v1.5) signature of the full URL for GET callbacks, or of the raw body for POST callbacks. Verify it with the key from /pubkey/.

Node.js
import { createVerify } from 'node:crypto'

export function verify(signedData, signature, pubkey) {
  return createVerify('sha256').update(signedData).verify(pubkey, signature, 'base64')
}
Python
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

def verify(signed_data: bytes, signature: str, pubkey_pem: str) -> bool:
    key = serialization.load_pem_public_key(pubkey_pem.encode())
    try:
        key.verify(base64.b64decode(signature), signed_data, padding.PKCS1v15(), hashes.SHA256())
        return True
    except Exception:
        return False
PHP
<?php
function verify(string $signedData, string $signature, string $pubkey): bool {
    return openssl_verify($signedData, base64_decode($signature), $pubkey, OPENSSL_ALGO_SHA256) === 1;
}

On this page