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.
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
POSTto 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:
- Open Workspace → Notifications → Delivery methods → Webhook → Connect.
- Give it a name and an HTTPS URL that returns
2xx. - Copy the signing secret — it is shown once. Store it as a server-side secret; you need it to verify every delivery.
- 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
}
}
| Field | Meaning |
|---|---|
id | Unique event id — use it to de-duplicate (delivery is at-least-once). |
type | Event type (see Events). |
created_at | When the event occurred (ISO-8601, UTC). |
audience | customer or reseller. |
data | Event-specific payload. |
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-TronAgg-Event | The event type. |
X-TronAgg-Delivery | Unique delivery id (stable across retries of the same delivery). |
X-TronAgg-Timestamp | Unix seconds when the request was signed. |
X-TronAgg-Signature | v1=<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.
| Event | When |
|---|---|
order.status_changed | An order reached a final state — data.status is COMPLETED or FAILED. Use this instead of polling. |
balance.credited | A deposit or transfer reached your balance. |
balance.low | Balance crossed below a threshold you set (sent once per crossing). |
account.daily_summary | Daily digest of orders, energy and spend. |
autoenergy.activated | An AutoEnergy subscription went active on an address. |
autoenergy.delegated | AutoEnergy billed one transfer on a subscribed address (opt-in — fires per transfer). |
autoenergy.closed | An 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.
The webhook data.status is upper-case (COMPLETED / FAILED), matching the live API.
Delivery & retries
- A delivery succeeds when your endpoint returns
2xxwithin 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(orX-TronAgg-Delivery). - Return
2xxfast and process asynchronously; do heavy work off the request path.
Next steps
- Track order status — the polling endpoint, still available for on-demand checks.
- Code examples — the full buy → track flow.