API REFERENCE

Webhook API
TRON address monitoring and transaction callbacks

Add an address to the watch list and every incoming transaction arrives at your server as an HTTP POST, signed with HMAC.

Service overview

The TRON monitoring service watches transaction activity on the addresses you register. When a watched address receives a TRX transfer, we send an HTTP POST to your callback URL in real time.

Base URL
https://api.rolthe.com/v1/tron-monitor
GET/tron-monitor/stats

Get statistics

Monitoring statistics for the current account: how many addresses are watched, quota usage and so on.

Response example

200 OK
{
  "success": true,
  "code": 0,
  "message": "ok",
  "data": {
    "total_addresses": 25,
    "active_addresses": 23,
    "total_callbacks": 156,
    "success_callbacks": 150,
    "failed_callbacks": 6
  }
}

Response fields

FieldTypeDescription
total_addressesnumberTotal addresses added to monitoring
active_addressesnumberAddresses currently being monitored
total_callbacksnumberTotal callbacks sent
success_callbacksnumberSuccessful callbacks
failed_callbacksnumberFailed callbacks
GET/tron-monitor/addresses

List monitored addresses

Paginated list of every address this account monitors.

Request parameters

ParameterTypeRequiredDescription
pagenumberNoPage number, 1 by default
page_sizenumberNoPage size, 20 by default, 100 maximum
statusstringNoFilter by status: active, inactive

Response example

200 OK
{
  "success": true,
  "code": 0,
  "message": "ok",
  "data": {
    "total": 25,
    "addresses": [
      {
        "address": "TExampleAddress1234567890abcdef",
        "label": "hot-wallet-1",
        "tokens": ["TRX"],
        "is_active": true,
        "total_callbacks": 10,
        "last_callback_at": "2024-01-02T15:45:00Z",
        "created_at": "2024-01-02T10:30:00Z"
      }
    ]
  }
}
POST/tron-monitor/addresses

Add a monitored address

Add a TRON address to the watch list. From then on, every incoming TRX transfer to it triggers a callback.

Request parameters

ParameterTypeRequiredDescription
addressstringYesThe TRON address to monitor (starts with T, 34 characters)
webhook_urlstringYesCallback URL (unless one is already configured on the API key)
labelstringNoA label or note, to make the address easy to recognise
tokensarrayNoWhich tokens to watch, ["TRX"] by default

Request example

POST /tron-monitor/addresses
curl -X POST "https://api.rolthe.com/v1/tron-monitor/addresses" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Timestamp: 1704067200" \
  -H "X-Signature: YOUR_SIGNATURE" \
  -H "Content-Type: application/json" \
  -d '{
    "address": "TExampleAddress1234567890abcdef",
    "webhook_url": "https://your-server.com/webhook",
    "label": "hot-wallet-1",
    "tokens": ["TRX"]
  }'

Response example

200 OK
{
  "success": true,
  "code": 0,
  "message": "address added to monitoring",
  "data": {
    "address": "TExampleAddress1234567890abcdef",
    "status": "active",
    "webhook_url": "https://your-server.com/webhook"
  }
}
DELETE/tron-monitor/addresses/{address}

Delete a monitored address

Remove an address from the watch list. No further callbacks are sent for it.

Request parameters

ParameterTypeRequiredDescription
addressstringYesThe TRON address to remove (path parameter)

Response example

200 OK
{
  "success": true,
  "code": 0,
  "message": "address removed from monitoring",
  "data": null
}

Webhook callback format

When a monitored address receives a TRX transfer, we send an HTTP POST to your configured callback URL.

POST /your-webhook-url
{
  "event": "tron.transfer",
  "timestamp": 1735689600,
  "data": {
    "tx_hash": "abc123def456789012345678901234567890abcd",
    "from": "TNcA4u3b8RhaCsdtq57Zgigx46MGhtaxAr",
    "to": "TExampleAddress1234567890abcdef",
    "amount": "100.000000",
    "token": "TRX",
    "block_number": 12345678,
    "block_timestamp": 1735689600000
  }
}

Response fields

FieldTypeDescription
eventstringEvent type: tron.transfer
timestampnumberWhen the callback was sent (Unix seconds)
data.tx_hashstringTransaction hash (64 hex characters)
data.fromstringSender address
data.tostringRecipient address (the monitored one)
data.amountstringFormatted amount (TRX)
data.tokenstringToken symbol: TRX
data.block_numbernumberBlock height
data.block_timestampnumberBlock timestamp (milliseconds)

Response requirements

Your server must return HTTP 200 within 5 seconds. The response body may be empty, or {"success": true}. Any non-2xx status triggers a retry.

Retry schedule

When a callback fails, we retry automatically on this schedule:

AttemptDelay
Retry 1after 10 seconds
Retry 2after 60 seconds
Retry 3after 5 minutes

Verifying the callback

So you can prove a callback really came from Rolthe, every request carries a signature. The signing key is the address-level webhook_secret; if that address has none of its own, we fall back to the API-key-level webhook_secret.

The API-key-level webhook_secret is returned alongside api_key and api_secret when you create the key, and you can view or copy it any time from User portal → API keys. It is not the same key as api_secret: api_secret signs the requests you send us, webhook_secret verifies the callbacks we send you. Do not mix them up.

Callback headers
X-Signature: abc123def456...
X-Timestamp: 1704067200
Content-Type: application/json

Verification steps

Pseudocode
# 1. Read the signature from the request headers
received_signature = headers['X-Signature']

# 2. Take the raw request body (the JSON string — do not deserialise and re-serialise)
request_body = request.get_data(as_text=True)

# 3. Compute the expected signature
# note: sign with webhook_secret, not api_secret
expected_signature = HMAC_SHA256(request_body, webhook_secret)

# 4. Compare (must be a constant-time function)
if not hmac.compare_digest(received_signature, expected_signature):
    return 403  # invalid signature

Note

Compare signatures with a constant-time function such as hmac.compare_digest. Using == returns early and leaks how far the comparison got, which in theory lets an attacker brute-force the signature byte by byte.

Receiver examples

webhook_receiver.py
from flask import Flask, request, jsonify
import hmac, hashlib

app = Flask(__name__)

# Address-level webhook_secret; falls back to the API-key-level one when unset
WEBHOOK_SECRET = "your_webhook_secret"
processed_txs = set()  # use a database in production — an in-memory set is lost on restart

def verify_signature(req):
    signature = req.headers.get('X-Signature', '')
    if not signature or not WEBHOOK_SECRET:
        return True  # no secret configured, skip verification

    # The raw body is required: re-serialising changes whitespace and key order, and the signature stops matching
    body = req.get_data(as_text=True)
    expected = hmac.new(
        WEBHOOK_SECRET.encode(), body.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

@app.route('/webhook', methods=['POST'])
def webhook():
    if not verify_signature(request):
        return jsonify({"error": "invalid signature"}), 403

    payload = request.get_json()
    tx_hash = payload['data']['tx_hash']

    # Idempotency: retries can deliver the same transaction more than once
    if tx_hash in processed_txs:
        return jsonify({"success": True})
    processed_txs.add(tx_hash)

    # Your business logic goes here, and it must be quick — 200 within 5 seconds
    print(f"received {payload['data']['amount']} {payload['data']['token']}")
    return jsonify({"success": True})
webhook_receiver.js
const express = require('express');
const crypto = require('crypto');

const app = express();
// Keep the raw body for verification: express.json() parses it, and re-serialising changes the bytes
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString(); } }));

const WEBHOOK_SECRET = 'your_webhook_secret';
const processedTxs = new Set();

function verifySignature(req) {
  const signature = req.headers['x-signature'] || '';
  if (!signature || !WEBHOOK_SECRET) return true;

  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.rawBody)
    .digest('hex');

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post('/webhook', (req, res) => {
  if (!verifySignature(req)) return res.status(403).json({ error: 'invalid signature' });

  const { tx_hash, amount, token } = req.body.data;
  if (processedTxs.has(tx_hash)) return res.json({ success: true });
  processedTxs.add(tx_hash);

  console.log(`received ${amount} ${token}`);
  res.json({ success: true });
});

app.listen(3000);

Callback never arrived?

The console shows the response code and body of every delivery, and lets you resend by hand.