PayAny Energy
My Wallets
API reference
OverviewAuthenticationPrice and termsOrdersOrder statusAccountAddresses and quick rentWebhooksErrorsExamples

Examples

Complete, dependency-free clients. Each one signs requests (HMAC or Bearer), unwraps the envelope, raises on errors and contains the webhook verifier. Copy the file into your project and set three environment variables:

Shell
export ENERGY_API_BASE=https://energy.payany.com
export ENERGY_ACCESS_ID=ek_…
export ENERGY_SECRET=…
export ENERGY_AUTH_MODE=hmac        # or bearer — must match the key

Then try it in test mode (nothing is charged):

Shell
python client.py account
python client.py price 65000 1h
python client.py order TXYZ… 65000 1h --external-id inv-1 --test
python client.py echo

Unit-test your own signer with the test vectors.

Python#

Python 3.10+, standard library only. verify_webhook checks an incoming webhook.

Python
#!/usr/bin/env python3
"""PayAny Energy reseller API — minimal Python client (standard library only).

Signing (HMAC keys):

    string_to_sign = ts + "\\n" + nonce + "\\n" + METHOD + "\\n" + path_with_query + "\\n" + raw_body
    X-Signature    = hex(HMAC_SHA256(secret, string_to_sign))

Usage:

    export ENERGY_API_BASE=http://127.0.0.1:8030
    export ENERGY_ACCESS_ID=ek_...  ENERGY_SECRET=...  ENERGY_AUTH_MODE=hmac   # or bearer
    python client.py account
    python client.py terms
    python client.py price 65000 1h
    python client.py order T... 65000 1h --external-id inv-1 --test
    python client.py get EN-XXXXXXXXXX
    python client.py echo

``verify_webhook`` checks an incoming webhook (see "Verify a webhook" in API.md).
"""

from __future__ import annotations

import argparse
import hashlib
import hmac
import json
import os
import secrets
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from collections.abc import Mapping
from typing import Any

SIGNATURE_WINDOW_SECONDS = 300


def sign(
    secret: str, timestamp: str, nonce: str, method: str, path_with_query: str, body: bytes
) -> str:
    """Hex HMAC-SHA256 of the canonical string (body = the exact bytes sent)."""
    message = f"{timestamp}\n{nonce}\n{method.upper()}\n{path_with_query}\n".encode() + body
    return hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()


def verify_webhook(
    secret: str,
    headers: Mapping[str, str],
    body: bytes,
    path_with_query: str,
    *,
    expected_access_id: str | None = None,
    tolerance: int = SIGNATURE_WINDOW_SECONDS,
    now: float | None = None,
) -> bool:
    """True when a webhook is authentic and fresh.

    ``path_with_query`` is the path (+ ``?query``) of YOUR callback URL, e.g.
    ``/hooks/energy``; ``body`` the raw request bytes (verify BEFORE parsing).
    Deduplicate on ``X-Delivery-Id`` afterwards: retries reuse it.
    """
    h = {k.lower(): v for k, v in headers.items()}
    timestamp = h.get("x-timestamp", "")
    nonce = h.get("x-nonce", "")
    given = h.get("x-signature", "")
    if expected_access_id is not None and h.get("x-access-id") != expected_access_id:
        return False
    if not timestamp.isdigit() or abs((now or time.time()) - int(timestamp)) > tolerance:
        return False
    expected = sign(secret, timestamp, nonce, "POST", path_with_query, body)
    return hmac.compare_digest(expected, given.strip().lower())


class ApiError(Exception):
    def __init__(self, status: int, code: int, message: str, data: Any = None) -> None:
        super().__init__(f"HTTP {status} code={code}: {message}")
        self.status, self.code, self.message, self.data = status, code, message, data


class EnergyClient:
    def __init__(
        self,
        base_url: str,
        access_id: str,
        secret: str,
        *,
        mode: str = "hmac",
        timeout: float = 15.0,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.access_id = access_id
        self.secret = secret
        self.mode = mode.lower()
        self.timeout = timeout

    def _auth_headers(self, method: str, path_with_query: str, body: bytes) -> dict[str, str]:
        if self.mode == "bearer":
            return {"Authorization": f"Bearer {self.access_id}.{self.secret}"}
        timestamp = str(int(time.time()))
        nonce = secrets.token_hex(16)
        return {
            "X-Access-Id": self.access_id,
            "X-Timestamp": timestamp,
            "X-Nonce": nonce,
            "X-Signature": sign(self.secret, timestamp, nonce, method, path_with_query, body),
        }

    def request(
        self,
        method: str,
        path: str,
        *,
        params: Mapping[str, Any] | None = None,
        json_body: Any = None,
        headers: Mapping[str, str] | None = None,
    ) -> Any:
        """Call ``path`` (e.g. ``/v1/account``); returns ``data`` or raises ApiError."""
        query = urllib.parse.urlencode({k: v for k, v in (params or {}).items() if v is not None})
        path_with_query = f"{path}?{query}" if query else path
        body = b"" if json_body is None else json.dumps(json_body, separators=(",", ":")).encode()
        all_headers = {
            "Accept": "application/json",
            **self._auth_headers(method, path_with_query, body),
        }
        if json_body is not None:
            all_headers["Content-Type"] = "application/json"
        all_headers.update(headers or {})
        if not self.base_url.startswith(("http://", "https://")):
            raise ValueError("base_url must be http(s)")
        req = urllib.request.Request(  # noqa: S310 — scheme checked above
            self.base_url + path_with_query,
            data=body if method.upper() != "GET" else None,
            method=method.upper(),
            headers=all_headers,
        )
        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as resp:  # noqa: S310
                status, raw, ctype = resp.status, resp.read(), resp.headers.get("Content-Type", "")
        except urllib.error.HTTPError as err:
            status, raw, ctype = err.code, err.read(), err.headers.get("Content-Type", "")
        if "text/csv" in ctype:
            return raw.decode()
        payload = json.loads(raw or b"{}")
        if status >= 400 or payload.get("code", 0) != 0:
            raise ApiError(
                status, payload.get("code", 0), payload.get("message", ""), payload.get("data")
            )
        return payload.get("data")

    # ── convenience ──
    def terms(self) -> Any:
        return self.request("GET", "/v1/terms")

    def price(self, energy: int, term: str) -> Any:
        return self.request("GET", "/v1/price", params={"energy": energy, "term": term})

    def account(self) -> Any:
        return self.request("GET", "/v1/account")

    def create_order(self, *, test_mode: bool = False, **order: Any) -> Any:
        headers = {"X-Test-Mode": "1"} if test_mode else None
        return self.request("POST", "/v1/orders", json_body=order, headers=headers)

    def get_order(self, order_no: str) -> Any:
        return self.request("GET", f"/v1/orders/{order_no}")

    def echo(self, payload: Any = None) -> Any:
        return self.request("POST", "/v1/auth/echo", json_body=payload or {"hello": "world"})


def _client_from_env() -> EnergyClient:
    try:
        return EnergyClient(
            os.environ.get("ENERGY_API_BASE", "http://127.0.0.1:8030"),
            os.environ["ENERGY_ACCESS_ID"],
            os.environ["ENERGY_SECRET"],
            mode=os.environ.get("ENERGY_AUTH_MODE", "hmac"),
        )
    except KeyError as missing:
        sys.exit(f"set {missing.args[0]} (and ENERGY_SECRET / ENERGY_AUTH_MODE)")


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="PayAny Energy reseller API client")
    sub = parser.add_subparsers(dest="cmd", required=True)
    sub.add_parser("account")
    sub.add_parser("terms")
    sub.add_parser("echo")
    p_price = sub.add_parser("price")
    p_price.add_argument("energy", type=int)
    p_price.add_argument("term")
    p_order = sub.add_parser("order")
    p_order.add_argument("energy_address")
    p_order.add_argument("energy", type=int)
    p_order.add_argument("term")
    p_order.add_argument("--external-id", default=None)
    p_order.add_argument("--charge-asset", default="TRX")
    p_order.add_argument("--max-price-trx", default=None)
    p_order.add_argument("--test", action="store_true", help="X-Test-Mode: 1 (nothing charged)")
    p_get = sub.add_parser("get")
    p_get.add_argument("order_no")
    args = parser.parse_args(argv)

    client = _client_from_env()
    try:
        if args.cmd == "account":
            out = client.account()
        elif args.cmd == "terms":
            out = client.terms()
        elif args.cmd == "echo":
            out = client.echo()
        elif args.cmd == "price":
            out = client.price(args.energy, args.term)
        elif args.cmd == "order":
            order = {
                "external_id": args.external_id or f"cli-{secrets.token_hex(6)}",
                "energy_address": args.energy_address,
                "energy": args.energy,
                "term": args.term,
                "charge_asset": args.charge_asset,
            }
            if args.max_price_trx:
                order["max_price_trx"] = args.max_price_trx
            out = client.create_order(test_mode=args.test, **order)
        else:
            out = client.get_order(args.order_no)
    except ApiError as err:
        print(json.dumps({"error": str(err), "data": err.data}, indent=2), file=sys.stderr)
        return 1
    print(json.dumps(out, indent=2, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    sys.exit(main())

Node.js#

Node 18+, no dependencies. verifyWebhook checks an incoming webhook (pass the raw body Buffer).

Node.js
// PayAny Energy reseller API — minimal Node.js client (Node 18+, no dependencies).
//
//   string_to_sign = ts + "\n" + nonce + "\n" + METHOD + "\n" + path_with_query + "\n" + raw_body
//   X-Signature    = hex(HMAC_SHA256(secret, string_to_sign))
//
// Usage:
//   ENERGY_API_BASE=http://127.0.0.1:8030 ENERGY_ACCESS_ID=ek_... ENERGY_SECRET=... \
//   ENERGY_AUTH_MODE=hmac node client.js account
//   node client.js price 65000 1h
//   node client.js order TXYZ... 65000 1h --test

'use strict';

const crypto = require('node:crypto');

function sign(secret, timestamp, nonce, method, pathWithQuery, body) {
  const head = Buffer.from(`${timestamp}\n${nonce}\n${method.toUpperCase()}\n${pathWithQuery}\n`, 'utf8');
  const raw = Buffer.isBuffer(body) ? body : Buffer.from(body || '', 'utf8');
  return crypto.createHmac('sha256', secret).update(Buffer.concat([head, raw])).digest('hex');
}

// Verify an incoming webhook. `headers` = request headers (any case), `rawBody` = Buffer of the
// exact bytes received, `pathWithQuery` = the path (+ query) of YOUR callback URL.
function verifyWebhook(secret, headers, rawBody, pathWithQuery, { expectedAccessId, tolerance = 300, now } = {}) {
  const h = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), String(v)]));
  const ts = h['x-timestamp'] || '';
  if (expectedAccessId !== undefined && h['x-access-id'] !== expectedAccessId) return false;
  if (!/^\d+$/.test(ts)) return false;
  const current = now !== undefined ? now : Date.now() / 1000;
  if (Math.abs(current - Number(ts)) > tolerance) return false;
  const expected = Buffer.from(sign(secret, ts, h['x-nonce'] || '', 'POST', pathWithQuery, rawBody), 'utf8');
  const given = Buffer.from((h['x-signature'] || '').trim().toLowerCase(), 'utf8');
  return expected.length === given.length && crypto.timingSafeEqual(expected, given);
}

class ApiError extends Error {
  constructor(status, code, message, data) {
    super(`HTTP ${status} code=${code}: ${message}`);
    Object.assign(this, { status, code, data });
  }
}

class EnergyClient {
  constructor(baseUrl, accessId, secret, { mode = 'hmac' } = {}) {
    this.baseUrl = baseUrl.replace(/\/+$/, '');
    this.accessId = accessId;
    this.secret = secret;
    this.mode = mode.toLowerCase();
  }

  authHeaders(method, pathWithQuery, body) {
    if (this.mode === 'bearer') return { Authorization: `Bearer ${this.accessId}.${this.secret}` };
    const ts = String(Math.floor(Date.now() / 1000));
    const nonce = crypto.randomBytes(16).toString('hex');
    return {
      'X-Access-Id': this.accessId,
      'X-Timestamp': ts,
      'X-Nonce': nonce,
      'X-Signature': sign(this.secret, ts, nonce, method, pathWithQuery, body),
    };
  }

  async request(method, path, { params, json, headers } = {}) {
    const query = new URLSearchParams(Object.entries(params || {}).filter(([, v]) => v !== undefined && v !== null)).toString();
    const pathWithQuery = query ? `${path}?${query}` : path;
    const body = json === undefined ? '' : JSON.stringify(json); // sign exactly what is sent
    const res = await fetch(this.baseUrl + pathWithQuery, {
      method,
      headers: {
        Accept: 'application/json',
        ...(json === undefined ? {} : { 'Content-Type': 'application/json' }),
        ...this.authHeaders(method, pathWithQuery, body),
        ...(headers || {}),
      },
      body: method === 'GET' ? undefined : body,
    });
    if ((res.headers.get('content-type') || '').includes('text/csv')) return res.text();
    const payload = await res.json();
    if (res.status >= 400 || payload.code !== 0) throw new ApiError(res.status, payload.code, payload.message, payload.data);
    return payload.data;
  }

  terms() { return this.request('GET', '/v1/terms'); }
  price(energy, term) { return this.request('GET', '/v1/price', { params: { energy, term } }); }
  account() { return this.request('GET', '/v1/account'); }
  createOrder(order, { testMode = false } = {}) {
    return this.request('POST', '/v1/orders', { json: order, headers: testMode ? { 'X-Test-Mode': '1' } : {} });
  }
  getOrder(orderNo) { return this.request('GET', `/v1/orders/${encodeURIComponent(orderNo)}`); }
  echo() { return this.request('POST', '/v1/auth/echo', { json: { hello: 'world' } }); }
}

async function main(argv) {
  const client = new EnergyClient(
    process.env.ENERGY_API_BASE || 'http://127.0.0.1:8030',
    process.env.ENERGY_ACCESS_ID,
    process.env.ENERGY_SECRET,
    { mode: process.env.ENERGY_AUTH_MODE || 'hmac' },
  );
  const [cmd, ...args] = argv;
  let out;
  if (cmd === 'account') out = await client.account();
  else if (cmd === 'terms') out = await client.terms();
  else if (cmd === 'echo') out = await client.echo();
  else if (cmd === 'price') out = await client.price(Number(args[0]), args[1]);
  else if (cmd === 'order') {
    out = await client.createOrder(
      { external_id: `js-${crypto.randomBytes(6).toString('hex')}`, energy_address: args[0], energy: Number(args[1]), term: args[2] },
      { testMode: args.includes('--test') },
    );
  } else if (cmd === 'get') out = await client.getOrder(args[0]);
  else throw new Error('usage: client.js account|terms|echo|price E T|order ADDR E T [--test]|get ORDER_NO');
  console.log(JSON.stringify(out, null, 2));
}

module.exports = { EnergyClient, ApiError, sign, verifyWebhook };

if (require.main === module) {
  main(process.argv.slice(2)).catch((err) => {
    console.error(err.message, err.data ? JSON.stringify(err.data) : '');
    process.exit(1);
  });
}

PHP#

PHP 8.1+ with ext-curl. energy_verify_webhook checks an incoming webhook.

PHP
<?php
// PayAny Energy reseller API — minimal PHP client (PHP 8.1+, ext-curl).
//
//   string_to_sign = ts . "\n" . nonce . "\n" . METHOD . "\n" . path_with_query . "\n" . raw_body
//   X-Signature    = hash_hmac('sha256', string_to_sign, secret)
//
// Usage:
//   ENERGY_API_BASE=http://127.0.0.1:8030 ENERGY_ACCESS_ID=ek_... ENERGY_SECRET=... \
//   ENERGY_AUTH_MODE=hmac php client.php account
//   php client.php price 65000 1h
//   php client.php order TXYZ... 65000 1h --test

declare(strict_types=1);

function energy_sign(string $secret, string $ts, string $nonce, string $method, string $pathWithQuery, string $body): string
{
    return hash_hmac('sha256', $ts . "\n" . $nonce . "\n" . strtoupper($method) . "\n" . $pathWithQuery . "\n" . $body, $secret);
}

/**
 * Verify an incoming webhook.
 * $headers: request headers (any case); $rawBody: file_get_contents('php://input');
 * $pathWithQuery: the path (+ query) of YOUR callback URL, e.g. '/hooks/energy'.
 */
function energy_verify_webhook(string $secret, array $headers, string $rawBody, string $pathWithQuery, ?string $expectedAccessId = null, int $tolerance = 300): bool
{
    $h = array_change_key_case($headers, CASE_LOWER);
    $ts = (string)($h['x-timestamp'] ?? '');
    if ($expectedAccessId !== null && ($h['x-access-id'] ?? '') !== $expectedAccessId) {
        return false;
    }
    if (!ctype_digit($ts) || abs(time() - (int)$ts) > $tolerance) {
        return false;
    }
    $expected = energy_sign($secret, $ts, (string)($h['x-nonce'] ?? ''), 'POST', $pathWithQuery, $rawBody);
    return hash_equals($expected, strtolower(trim((string)($h['x-signature'] ?? ''))));
}

final class EnergyApiError extends RuntimeException
{
    public function __construct(public int $status, public int $apiCode, string $message, public mixed $data = null)
    {
        parent::__construct("HTTP {$status} code={$apiCode}: {$message}");
    }
}

final class EnergyClient
{
    public function __construct(
        private string $baseUrl,
        private string $accessId,
        private string $secret,
        private string $mode = 'hmac',
    ) {
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->mode = strtolower($mode);
    }

    private function authHeaders(string $method, string $pathWithQuery, string $body): array
    {
        if ($this->mode === 'bearer') {
            return ["Authorization: Bearer {$this->accessId}.{$this->secret}"];
        }
        $ts = (string)time();
        $nonce = bin2hex(random_bytes(16));
        return [
            "X-Access-Id: {$this->accessId}",
            "X-Timestamp: {$ts}",
            "X-Nonce: {$nonce}",
            'X-Signature: ' . energy_sign($this->secret, $ts, $nonce, $method, $pathWithQuery, $body),
        ];
    }

    public function request(string $method, string $path, array $params = [], mixed $json = null, array $headers = []): mixed
    {
        $query = http_build_query(array_filter($params, fn ($v) => $v !== null), '', '&', PHP_QUERY_RFC3986);
        $pathWithQuery = $query !== '' ? "{$path}?{$query}" : $path;
        $body = $json === null ? '' : json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $all = array_merge(['Accept: application/json'], $json === null ? [] : ['Content-Type: application/json'],
            $this->authHeaders($method, $pathWithQuery, $body), $headers);
        $ch = curl_init($this->baseUrl . $pathWithQuery);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => $all,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 15,
        ]);
        if ($method !== 'GET') {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }
        $raw = curl_exec($ch);
        $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $ctype = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
        curl_close($ch);
        if ($raw === false) {
            throw new EnergyApiError(0, 0, 'network error');
        }
        if (str_contains($ctype, 'text/csv')) {
            return $raw;
        }
        $payload = json_decode($raw, true);
        if ($status >= 400 || ($payload['code'] ?? -1) !== 0) {
            throw new EnergyApiError($status, (int)($payload['code'] ?? 0), (string)($payload['message'] ?? ''), $payload['data'] ?? null);
        }
        return $payload['data'];
    }

    public function terms(): mixed { return $this->request('GET', '/v1/terms'); }
    public function price(int $energy, string $term): mixed { return $this->request('GET', '/v1/price', ['energy' => $energy, 'term' => $term]); }
    public function account(): mixed { return $this->request('GET', '/v1/account'); }
    public function createOrder(array $order, bool $testMode = false): mixed
    {
        return $this->request('POST', '/v1/orders', [], $order, $testMode ? ['X-Test-Mode: 1'] : []);
    }
    public function getOrder(string $orderNo): mixed { return $this->request('GET', '/v1/orders/' . rawurlencode($orderNo)); }
}

if (PHP_SAPI === 'cli' && realpath($argv[0]) === __FILE__) {
    $client = new EnergyClient(
        getenv('ENERGY_API_BASE') ?: 'http://127.0.0.1:8030',
        (string)getenv('ENERGY_ACCESS_ID'),
        (string)getenv('ENERGY_SECRET'),
        getenv('ENERGY_AUTH_MODE') ?: 'hmac',
    );
    $cmd = $argv[1] ?? '';
    try {
        $out = match ($cmd) {
            'account' => $client->account(),
            'terms' => $client->terms(),
            'price' => $client->price((int)$argv[2], $argv[3]),
            'order' => $client->createOrder(
                ['external_id' => 'php-' . bin2hex(random_bytes(6)), 'energy_address' => $argv[2], 'energy' => (int)$argv[3], 'term' => $argv[4]],
                in_array('--test', $argv, true),
            ),
            'get' => $client->getOrder($argv[2]),
            default => throw new InvalidArgumentException('usage: client.php account|terms|price E T|order ADDR E T [--test]|get ORDER_NO'),
        };
        echo json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;
    } catch (EnergyApiError $e) {
        fwrite(STDERR, $e->getMessage() . PHP_EOL);
        exit(1);
    }
}
PreviousErrors
PayAny Energy

TRON energy for USDT transfers — a PayAny product.

Scan to download the app
Product
  • Buy Energy
  • My Wallets
  • Pricing
  • Find an Order
  • Telegram Bot ↗
Developers
  • API Docs
  • API Keys
Company
  • FAQ
  • Terms of Service
  • Privacy Policy
  • Delete your account
  • PayAny.com ↗
© 2026 PayAny. All rights reserved.Energy is delegated on the TRON network; delivery times depend on network confirmation.