Webhook events
Webhooks are the optional push channel: instead of polling, TRONAgg POSTs a signed JSON event to your URL when something happens. Polling (GET /order/{id}, GET /deposits) always works without any of this — set up a webhook only if you want push.
Create one in Workspace → Notifications or with POST /webhooks.
The delivery
Every delivery is one HTTPS POST with one event:
{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "order.status_changed",
"created_at": "2026-07-12T10:30:00Z",
"audience": "customer",
"data": { }
}
| Field | Meaning |
|---|---|
id | Unique event id — de-duplicate on this. |
type | Event type, also in the X-TronAgg-Event header. |
created_at | When it happened (ISO-8601, UTC). |
audience | Always customer for your webhooks. |
data | Event-specific payload — see the catalog. |
| Header | Value |
|---|---|
Content-Type | application/json |
X-TronAgg-Event | The event type. |
X-TronAgg-Delivery | Delivery id, stable across retries. |
X-TronAgg-Timestamp | Unix seconds when the delivery was signed. |
X-TronAgg-Signature | v1=<hex> — verify it. |
Answer 2xx within a few seconds and do heavy work off the request path. Failed deliveries are retried with backoff, so the same event can arrive twice — de-duplicate on id.
Verify the signature
The signature is HMAC-SHA256 over "{timestamp}.{raw_body}" with your webhook's signing secret (shown once, at creation), hex-encoded as v1=<hex>. Verify over the raw request bytes, compare in constant time, and reject stale timestamps:
import hashlib, hmac
def verify(secret: str, timestamp: str, body: bytes, signature_header: str) -> bool:
expected = hmac.new(
secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"v1={expected}", signature_header)
The same check in JavaScript (Node)
const crypto = require("node:crypto");
function verify(secret, timestamp, rawBody, signatureHeader) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody) // Buffer of the exact bytes received
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(`v1=${expected}`),
Buffer.from(signatureHeader)
);
}
Events
On events fire out of the box on a webhook created via POST /webhooks; Off events start disabled — toggle any of them per webhook in Workspace → Notifications.
| Event | Fires when | Default |
|---|---|---|
order.status_changed | An order finishes — COMPLETED or FAILED. | ✅ On |
balance.credited | A deposit or credit lands on your balance. | ✅ On |
autoenergy.delegated | AutoEnergy covers one transfer. | ✅ On |
autoenergy.closed | An AutoEnergy subscription stops. | ✅ On |
autoenergy.activated | An AutoEnergy subscription becomes active. | — Off |
balance.low | Balance drops below your threshold. | — Off |
account.daily_summary | Once-a-day account digest. | — Off |
order.status_changed
The push alternative to polling GET /order/{id}. Fires once the order is final.
{
"order_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "COMPLETED",
"resource_type": "ENERGY",
"resource_amount": "65000",
"receiver_address": "TC7UXt7t2WLGbFasqvErydRQZ7bnGbQs",
"total_price_sun": 3500000,
"error_message": null
}
| Field | Meaning |
|---|---|
status | COMPLETED or FAILED (upper-case, matching the API). |
resource_amount | Energy amount, as a string. |
total_price_sun | What you were charged, in SUN. |
error_message | Set only when status is FAILED. |
balance.credited
{
"entry_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"entry_type": "deposit",
"amount_sun": 100000000,
"balance_sun": 250000000,
"reference_id": "a1b2c3d4e5f6..."
}
| Field | Meaning |
|---|---|
amount_sun | Amount credited (SUN). |
balance_sun | Your balance after the credit (SUN). |
entry_type | deposit for on-chain deposits. |
reference_id | Source of the credit, e.g. the deposit transaction. |
autoenergy.delegated
Fires per covered transfer on a subscribed address.
{
"subscription_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"address": "TC7UXt7t2WLGbFasqvErydRQZ7bnGbQs",
"energy_class": "131k",
"billed_sun": 2100000,
"used_tx_id": "a1b2c3d4e5f6...",
"tx_used_total": 5,
"quota_max_tx": 100
}
| Field | Meaning |
|---|---|
energy_class | Energy covered for this transfer — 65k or 131k. |
billed_sun | What was billed for it (SUN). |
used_tx_id | The transfer that consumed the energy. |
tx_used_total / quota_max_tx | Transfers covered so far / quota (null = uncapped). |
autoenergy.closed
Fires when a subscription stops, for any reason.
{
"subscription_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"address": "TC7UXt7t2WLGbFasqvErydRQZ7bnGbQs",
"close_reason": "quota",
"tx_used_total": 42
}
| Field | Meaning |
|---|---|
close_reason | Why it stopped — quota, expired, idle, insufficient_balance, manual, … Treat as an open set. |
tx_used_total | Total transfers the subscription covered. |
autoenergy.activated
Fires when a subscription becomes active on an address.
{
"subscription_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"address": "TC7UXt7t2WLGbFasqvErydRQZ7bnGbQs"
}
balance.low
Fires once per crossing of the threshold you set when enabling it.
{
"entry_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"entry_type": "purchase",
"amount_sun": -3500000,
"balance_sun": 12000000,
"reference_id": "a1b2c3d4e5f6...",
"threshold_sun": 50000000
}
| Field | Meaning |
|---|---|
balance_sun | Balance after the change that crossed the threshold (SUN). |
threshold_sun | The level you configured (SUN). |
amount_sun | The change itself — negative for a debit. |
account.daily_summary
{
"date": "2026-07-23",
"completed_orders": 12,
"energy_received": "780000",
"spent_sun": 42000000,
"balance_sun": 250000000
}
| Field | Meaning |
|---|---|
completed_orders | Orders completed that day. |
energy_received | Total energy delegated to you (string). |
spent_sun / balance_sun | Spend that day / end-of-day balance (SUN). |
Test your endpoint
POST /webhooks/{id}/test sends a notification.test event, signed exactly like a real delivery — use it to confirm your endpoint and signature check end to end:
{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "notification.test",
"created_at": "2026-07-12T10:30:00Z",
"data": { "message": "TRONAgg notifications are connected." }
}
Next steps
- Create a webhook — register one with your API key.
- Webhooks & Notifications — setup, channels, and the workspace flow.
- Track order status — polling, no webhook required.