Public integration guide. API base URL: https://api.toknex.space. Confirm network settings and one-time fees in your merchant account.

Webhooks

Webhooks tell your server about payments the moment they happen, so you don't need to poll.

Set up

  1. Dashboard → Webhooks → enter your HTTPS endpoint, for example https://yourshop.com/webhooks/usdt.
  2. Save. A signing secret whsec_… is shown once. Store it as GATEWAY_WEBHOOK_SECRET.
  3. Press Send test event. A ping event is delivered within seconds and appears in the deliveries list with its HTTP status.

Requirements: a public https:// URL (private and local network addresses are refused), a 2xx answer within 10 seconds, and no redirects (they are not followed).

Events

TypeWhenTypical action
deposit.detectedA transfer to a customer's address appears on chainShow "payment detected, confirming…"
deposit.confirmedaccount-configured confirmations confirmations reached and verified; your balance is creditedCredit the customer / fulfil the order
deposit.collectedFunds swept to the gateway treasuryOptional bookkeeping
settlement.approvedYour payout request was approvedOptional
settlement.paidYour payout was sent; data.payout_tx_hash is setReconcile your wallet
pingYou pressed "Send test event"Answer 200

Payload

POST /webhooks/usdt HTTP/1.1
Content-Type: application/json
User-Agent: USDT-Gateway-Webhook/1.0
X-Gateway-Event: deposit.confirmed
X-Gateway-Delivery: evt_7731
X-Gateway-Signature: t=1792915965,v1=5b0c9d…

{"id":"evt_7731","type":"deposit.confirmed","created":1792915965,"data":{ …deposit object… }}

For deposit.* events, data is the deposit object. For settlements, data is {id, amount, destination_address, status, payout_tx_hash}.

Verify the signature

The v1 value is HMAC-SHA256(secret, t + "." + raw_body) in hex. Always:

  1. Use the raw request body exactly as received, before any JSON parsing.
  2. Reject a timestamp more than 5 minutes old (this stops replays).
  3. Compare with a constant-time function.

PHP

$raw = file_get_contents('php://input');
if (!preg_match('/t=(\d+),v1=([0-9a-f]{64})/', $_SERVER['HTTP_X_GATEWAY_SIGNATURE'] ?? '', $m)) { http_response_code(400); exit; }
if (abs(time() - (int) $m[1]) > 300) { http_response_code(400); exit; }
$expected = hash_hmac('sha256', $m[1] . '.' . $raw, getenv('GATEWAY_WEBHOOK_SECRET'));
if (!hash_equals($expected, $m[2])) { http_response_code(401); exit; }

Node.js (Express): register the route with express.raw({ type: 'application/json' }) so you get the raw body:

const [, t, v1] = (req.get('X-Gateway-Signature') || '').match(/t=(\d+),v1=([0-9a-f]{64})/) || [];
const expected = crypto.createHmac('sha256', process.env.GATEWAY_WEBHOOK_SECRET).update(`${t}.${req.body}`).digest('hex');
const ok = t && Math.abs(Date.now() / 1000 - Number(t)) <= 300 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));

Python (Flask)

raw = request.get_data()
m = re.match(r"t=(\d+),v1=([0-9a-f]{64})", request.headers.get("X-Gateway-Signature", ""))
expected = hmac.new(SECRET.encode(), f"{m[1]}.".encode() + raw, hashlib.sha256).hexdigest()
ok = m and abs(time.time() - int(m[1])) <= 300 and hmac.compare_digest(expected, m[2])

Retries, ordering, duplicates

Rotating the secret

Webhooks → tick Generate a new signing secret → Save. Update GATEWAY_WEBHOOK_SECRET on your server right away; events sent after the rotation are signed with the new secret.