Request Example and Signature Verification
Request method: APIs mainly use `GET` / `POST`; JSON requests use `application/json`.
Authentication: OpenAPI requests are verified with request headers and HMAC-SHA256 signatures.
Request Headers
| Field | Type | Required | Description |
|---|---|---|---|
| x-api-key | string | Yes | Merchant API Key. Use the value beginning with mch_ from the merchant API settings page. |
| x-merchant-uid | string | Yes | Merchant UID. |
| x-timestamp | string | Yes | 13-digit millisecond timestamp. The default allowed clock drift is 5 minutes. |
| x-nonce | string | Yes | Unique random string for each request, up to 128 characters. Reuse is rejected. |
| x-signature | string | Yes | Hex signature generated with HMAC-SHA256. The payload contains Merchant UID, timestamp, nonce, method, path, canonical query, and canonical body. |
Signature Rules
- Requests use application/json. For GET APIs with no body, the body part in the signature is an empty string.
- The signature path must be the raw API path, for example /openapi/payin/orders, without the domain.
- Query parameters are sorted by key; JSON body keys are sorted before serialization.
- Common signature failures include expired timestamps, repeated nonce values, inconsistent field order, or extra spaces.
The fixed timestamp, nonce and credentials below are offline signature test values. Use the current millisecond timestamp and a new random nonce for real requests. API paths have no version prefix; v1 refers only to callback signing.
Signature Payload Example
880001
1776193200000
2f5c7b147c3748f0a8b3d9bb38aa91a4
POST
/openapi/payin/orders
{"amount":"100.00","chainCode":"TRON","merchantOrderNo":"M202604150001","notifyUrl":"https://merchant.example.com/api/uugate/payin-notify","tokenSymbol":"USDT"}Full Request Example
POST /openapi/payin/orders HTTP/1.1
Host: api.uugate.com
Content-Type: application/json
x-api-key: mch_xxxxxxxxxxxxxxxxxxxx
x-merchant-uid: 880001
x-timestamp: 1776193200000
x-nonce: 2f5c7b147c3748f0a8b3d9bb38aa91a4
x-signature: 6d7e96fdbf3ec37c1ec515540f46c6c19b08754973c43293e54644c2da910838
{
"chainCode": "TRON",
"tokenSymbol": "USDT",
"merchantOrderNo": "M202604150001",
"amount": "100.00",
"notifyUrl": "https://merchant.example.com/api/uugate/payin-notify"
}Signature Code Example
- All four languages use the same endpoint paths, parameters and response contract. Credentials come from UUGATE_MERCHANT_UID and UUGATE_API_KEY; UUGATE_BASE_URL defaults to https://api.uugate.com.
- canonicalBody is compact JSON with sorted field names. After changing parameters, sort object keys recursively, preserve array order and use UTF-8 without extra slash or Unicode escaping. Amounts are strings; booleans are true / false.
- Successful responses return the business object directly, without a code / data wrapper. Check HTTP status before processing the body. Business errors return code, message, data and requestId; security denials may only return HTTP 401 / 403 with an empty body.
- After a timeout or HTTP 5xx, query the order before retrying creation or submission. Generate a new timestamp, nonce and signature for every new request.
Run: save Java as UUGateExample.java (UUGateCallback.java for callbacks) and use java filename; PHP: php example.php; Go: go run example.go; Node.js: node example.mjs. Callback verifiers read the raw body from standard input.
import { createHmac, randomBytes } from 'node:crypto';
function required(name) {
const value = process.env[name]?.trim();
if (!value) throw new Error(name + ' is required');
return value;
}
const uid = "880001", key = "mch_xxxxxxxxxxxxxxxxxxxx";
const timestamp = "1776193200000", nonce = "2f5c7b147c3748f0a8b3d9bb38aa91a4";
const method = 'POST', path = '/openapi/payin/orders';
const canonicalQuery = '', canonicalBody = "{\"amount\":\"100.00\",\"chainCode\":\"TRON\",\"merchantOrderNo\":\"M202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payin-notify\",\"tokenSymbol\":\"USDT\"}";
const payload = [uid, timestamp, nonce, method, path, canonicalQuery, canonicalBody].join('\n');
const signature = createHmac('sha256', key).update(payload, 'utf8').digest('hex');
console.log(signature);import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class UUGateExample {
static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) throw new IllegalArgumentException(name + " is required");
return value.trim();
}
public static void main(String[] args) throws Exception {
String uid = "880001", key = "mch_xxxxxxxxxxxxxxxxxxxx";
String timestamp = "1776193200000", nonce = "2f5c7b147c3748f0a8b3d9bb38aa91a4";
String method = "POST", path = "/openapi/payin/orders";
String canonicalQuery = "", canonicalBody = "{\"amount\":\"100.00\",\"chainCode\":\"TRON\",\"merchantOrderNo\":\"M202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payin-notify\",\"tokenSymbol\":\"USDT\"}";
String payload = String.join("\n", uid, timestamp, nonce, method, path, canonicalQuery, canonicalBody);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = HexFormat.of().formatHex(mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)));
System.out.println(signature);
}
}<?php
function required(string $name): string {
$value = trim((string) getenv($name));
if ($value === '') throw new RuntimeException($name . ' is required');
return $value;
}
$uid = "880001"; $key = "mch_xxxxxxxxxxxxxxxxxxxx";
$timestamp = "1776193200000"; $nonce = "2f5c7b147c3748f0a8b3d9bb38aa91a4";
$method = 'POST'; $path = '/openapi/payin/orders';
$canonicalQuery = ''; $canonicalBody = '{"amount":"100.00","chainCode":"TRON","merchantOrderNo":"M202604150001","notifyUrl":"https://merchant.example.com/api/uugate/payin-notify","tokenSymbol":"USDT"}';
$payload = implode("\n", [$uid, $timestamp, $nonce, $method, $path, $canonicalQuery, $canonicalBody]);
$signature = hash_hmac('sha256', $payload, $key);
echo $signature, PHP_EOL;package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"strings"
)
func required(name string) string {
value := strings.TrimSpace(os.Getenv(name))
if value == "" { panic(name + " is required") }
return value
}
func main() {
uid, key := "880001", "mch_xxxxxxxxxxxxxxxxxxxx"
timestamp, nonce := "1776193200000", "2f5c7b147c3748f0a8b3d9bb38aa91a4"
method, path := "POST", "/openapi/payin/orders"
canonicalQuery, canonicalBody := "", "{\"amount\":\"100.00\",\"chainCode\":\"TRON\",\"merchantOrderNo\":\"M202604150001\",\"notifyUrl\":\"https://merchant.example.com/api/uugate/payin-notify\",\"tokenSymbol\":\"USDT\"}"
payload := strings.Join([]string{uid, timestamp, nonce, method, path, canonicalQuery, canonicalBody}, "\n")
mac := hmac.New(sha256.New, []byte(key)); mac.Write([]byte(payload))
signature := hex.EncodeToString(mac.Sum(nil))
fmt.Println(signature)
}Ordinary business errors return JSON with code, message, data and requestId. Invalid keys, signatures, timestamps, nonces or security restrictions may return HTTP 401 / 403 with an empty body and no business code. In every language, check the HTTP status and body before parsing JSON. The localized message is not a decision key.
Error Response Fields
| Field | Type | Required | Description |
|---|---|---|---|
| code | number | Yes | Business error code. |
| message | string | Yes | Error message. |
| data | object | null | Yes | Additional error data. |
| requestId | string | null | Yes | Server request trace ID. |
Error Response Example
HTTP/1.1 401 Unauthorized
Cache-Control: no-store
Content-Length: 0