Skip to main content

Webhooks

Your account has one webhook URL, and every event goes to it. Set it in Dashboard → API, or with Configure Webhook.

Events

Six events exist today.
order.updated is sent only for orders that were created with an API key. Orders placed on the website or through the Telegram bot produce no webhook, even though the same account receives Smart Mode events normally.

Envelope

Every delivery has the same three top-level keys. timestamp is the moment we built the event, ISO-8601 in UTC.
Response
Headers:

order.updated

number
Order ID.
string
energy or bandwidth.
string
processing, filled, partial, failed, reclaimed or reclaim_failed. This field is the source of truth — see the warning below.
string
The address the resource was delegated to.
number
The ordered amount — energy units, or bandwidth points.
number
Same as volume. Present so that expected and delivered can be compared without knowing which field means what.
number
How much has actually been delegated so far. Equals volume once the order is filled; on processing and partial it is the sum of the chunks delegated up to now, and 0 on failure.
string
Order price in TRX, as a decimal string.
string | null
Transaction that delegated the resource.
string | null
Transaction that took it back, once the rental ends.
string | null
When the resource is scheduled to be reclaimed, ISO-8601, or null for orders that are not reclaimed on a timer.
Response
Judge the outcome by status, never by the presence of delegate_txid. A failed order can carry a transaction id — we keep it deliberately so that a failure can be investigated. Only status says whether you got the resource.
A large order is split into chunks internally, but that is our business: you receive one webhook per order, never one per chunk.

subscription.transfer

number
Smart Mode subscription this transfer belongs to.
string
The watched address that sent the transfer.
string | null
The label you gave the subscription.
string
The transfer transaction on chain.
number
How many transfers this delegation covered.
number
Energy delegated for them.
string
What was charged for the energy, in TRX.
string | null
Bandwidth charged on top, or null when none was.
string
Your account balance after the charge.
string
When the transfer was processed. Note this is inside data and is separate from the envelope’s own timestamp.
Response

Transfer Package events

The four package.* events go to the account that owns the package. A package bought by invoice without signing in or an API key has no owner and produces no events. Cancelled invoices, refused purchases and purchases held for manual review produce no events either; a held package sends package.activated once it is activated. Every package.* event carries these fields in data:
number
Package ID — the id in List Packages.
string
The address the package serves.
string
Package status after the event: active or completed for package.transfer, completed for package.completed, active for package.activated, expired for package.expired.
number
Transfers the package was bought with.
number
Transfers left on this package after the change the event reports — an absolute number, not a difference.
Events can arrive out of order. Each one is delivered on its own, with retries for up to about 35 seconds, so the package.completed caused by a transfer can arrive before that transfer’s package.transfer, and a package.transfer can arrive before package.activated. For each package_id, keep the lowest remaining you have seen: it never grows. completed and expired are final.
remaining can also go down without an event. A multi-transfer package takes one transfer a day as its daily subscription — the first one at activation, which is why a 500-transfer package starts at remaining: 499 — and that charge is not announced. Two transfers from the same address in the same block can report the same remaining. Treat remaining as a hint and List Packages as the source of truth.
To recognise a retry, use (package_id, tx_hash) for package.transfer and (event, package_id) for the others: a retry carries the same body, the same timestamp and the same signature.

package.transfer

A transfer from the address was paid from a package. Sent once per package the transfer was charged to — almost always one event per transfer; a transfer that took the last transfer of one package and continued on the next produces one event for each.
string
The transfer transaction on chain.
number
Transfers taken from this package for this transaction. A transfer to an address that holds no USDT needs double energy and counts as 2, which can be split across two packages.
Here status is completed when this transfer used up the package.
Response

package.completed

The package stopped serving transfers.
string
transfer — a transfer used up the package; subscription — the daily charge used it up; manual — support closed the package. After manual, remaining can be above zero, but those transfers can no longer be used.
string | null
For reason: "transfer", the transfer that closed the package; null otherwise.
A package with nothing left — for example a regular one-transfer package, whose first daily charge took its only transfer — is closed by the next transfer without a package.transfer of its own: you receive only package.completed with that transfer’s tx_hash.
Response

package.activated

The package became active and its transfers get energy from now on. Sent for every activation — including a purchase from balance, whose response already says active, and an invoice payment, which activates the package shortly after the payment is seen on chain.
string
How the package was paid: exactly balance, TRX or USDT.
Response

package.expired

An invoice for a package was not paid within its 30-minute window. Nothing was charged; buy again to get a new invoice. No extra fields; remaining equals quota_total and is not available to use.
Response

Delivery

  • Up to 3 attempts, with pauses of 1 second and then 4 seconds between them. There is no pause after a success and none after the last attempt.
  • 10 seconds timeout per attempt.
  • We expect any 2xx. Anything else counts as a failed attempt.
  • Redirects are not followed. A 301/302 is a failed attempt, not a hop.
  • Missed events are not backfilled. When all three attempts fail, that event is gone; reconcile with Get Order or List Packages rather than waiting for a replay.
Your URL must resolve to a public address. Loopback, private and link-local ranges are rejected, and so is a host that does not resolve — in both cases nothing is sent and no log row is written.

Verifying the signature

Every request carries X-Webhook-Signature: sha256=<hex>, an HMAC-SHA256 over the raw request body keyed with your webhook secret. Two details decide whether your check works:
  1. Strip the sha256= prefix before comparing. The header is not a bare hex digest.
  2. Hash the bytes you received, not a re-serialized object. Parsing the JSON and dumping it again changes key order and spacing, and the signature will never match.
Your signing secret is shown in Dashboard → API next to the webhook URL. It is not returned to API-key requests — reading it requires signing in on the website.
Verify the signature before acting on the payload, and compare in constant time (hmac.compare_digest / crypto.timingSafeEqual). Comparing with == leaks timing.