Skip to main content

Webhooks & Notifications

Instead of polling GET /order/{id} in a loop, let TRONAgg push you an event the moment something happens. Connect a webhook (or Telegram) once, and you receive a signed JSON request for each event you subscribe to.

Poll or subscribe

GET /order/{id} is still there for on-demand checks. But for order status you don't have to poll — subscribe to the order.status_changed webhook and update your own record when the COMPLETED / FAILED event arrives.

Channels

Notifications are delivered over one of two channels; the events and preferences are the same for both:

  • Webhook — a signed JSON POST to your backend. For developers and automation.
  • Telegram — readable alerts in a Telegram chat. No code.

Set up a webhook

Webhooks are configured in the Workspace, not through the API:

  1. Open Workspace → Notifications → Delivery methods → Webhook → Connect.
  2. Give it a name and an HTTPS URL that returns 2xx.
  3. Copy the signing secret — it is shown once. Store it as a server-side secret; you need it to verify every delivery.
  4. Pick which events go to this webhook in the Notifications tab. Use Send test to fire a sample delivery, and Delivery history to see the last attempts.

Event format

Every delivery is a POST with a JSON body shaped like this:

{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "order.status_changed",
"created_at": "2026-07-12T10:30:00Z",
"audience": "customer",
"data": {
"order_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "COMPLETED",
"resource_type": "ENERGY",
"resource_amount": "65000",
"receiver_address": "TC7UXt...GbQs",
"total_price_sun": 3500000,
"error_message": null
}
}
FieldMeaning
idUnique event id — use it to de-duplicate (delivery is at-least-once).
typeEvent type (see Events).
created_atWhen the event occurred (ISO-8601, UTC).
audiencecustomer or reseller.
dataEvent-specific payload.

Headers

HeaderValue
Content-Typeapplication/json
X-TronAgg-EventThe event type.
X-TronAgg-DeliveryUnique delivery id (stable across retries of the same delivery).
X-TronAgg-TimestampUnix seconds when the request was signed.
X-TronAgg-Signaturev1=<hex> — HMAC signature (see below).

Verify the signature

Always verify before trusting a delivery. The signature is:

hex( HMAC_SHA256( secret, timestamp + "." + rawBody ) )

Compute it over the raw request body (not a re-serialized object) and the X-TronAgg-Timestamp header, then compare against the hex after v1= using a constant-time comparison. Reject deliveries whose timestamp is too old to blunt replay attacks.

import hashlib
import hmac
import time

def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
timestamp = headers["X-TronAgg-Timestamp"]
signature = headers["X-TronAgg-Signature"].removeprefix("v1=")

# Reject anything older than 5 minutes (replay protection).
if abs(time.time() - int(timestamp)) > 300:
return False

expected = hmac.new(
secret.encode(),
timestamp.encode() + b"." + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
import crypto from 'node:crypto';

function verify(secret, headers, rawBody) {
const timestamp = headers['x-tronagg-timestamp'];
const signature = headers['x-tronagg-signature'].replace(/^v1=/, '');

if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');

return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Events

Subscribe per-event in the Workspace. The most useful one for integrations is order.status_changed.

EventWhen
order.status_changedAn order reached a final state — data.status is COMPLETED or FAILED. Use this instead of polling.
balance.creditedA deposit or transfer reached your balance.
balance.lowBalance crossed below a threshold you set (sent once per crossing).
account.daily_summaryDaily digest of orders, energy and spend.
autoenergy.activatedAn AutoEnergy subscription went active on an address.
autoenergy.delegatedAutoEnergy billed one transfer on a subscribed address (opt-in — fires per transfer).
autoenergy.closedAn AutoEnergy subscription closed — data.close_reason says why (idle, quota, expired, insufficient_balance, manual, …).

order.status_changed data carries order_id, status, resource_type, resource_amount, receiver_address, total_price_sun and error_message (set when FAILED). Reseller-audience deliveries also include the customer and per-order revenue.

AutoEnergy data fields: autoenergy.activated carries subscription_id and address; autoenergy.delegated adds energy_class (65k/131k), billed_sun and used_tx_id; autoenergy.closed adds close_reason and tx_used_total. Example payloads for every event are in the Workspace notifications page.

Status casing

The webhook data.status is upper-case (COMPLETED / FAILED), matching the live API.

Delivery & retries

  • A delivery succeeds when your endpoint returns 2xx within 10 seconds. Redirects are not followed.
  • Failed deliveries are retried with backoff (roughly 1m → 5m → 30m → 2h → 12h) before being given up.
  • Delivery is at-least-once — the same event may arrive more than once. De-duplicate on the event id (or X-TronAgg-Delivery).
  • Return 2xx fast and process asynchronously; do heavy work off the request path.

Next steps