logo

Search...

⌘ K

English

Product Guide
API Reference
Resource Download
Help Center

Getting Started

Overview

Scenario Capabilities

Onboarding Steps

Payin Capabilities

CKP Checkout Page

Interaction

Integration Prep

Integration Steps

Deeplink Redirect

Interaction

Integration Prep

Integration Steps

QRCode Display

Interaction

Integration Prep

Integration Steps

Payout Capabilities

Capability Overview

Deposit

Crypto Withdraw / Payout

Fiat Withdraw

Receive Payout Result

Supported Chains and Currencies

Supported Chains

Supported Currencies

Appendix

Wallets / Currencies / Chains

Transaction Status

Payment Status

Refund Status

Withdraw Status

Batch Status

Error Code Description

Signature Algorithm

Signature Algorithm

Algorithm Description

Fields participating in the signature:

  • apiPath: Request URI path (required), excluding host, query string, and the /crypto-payment prefix, e.g. /payin/v1/createPayment
  • body: Raw JSON string of the request body (required), byte-for-byte identical to what is sent to Paydify
  • x-api-key: Same as appId (required)
  • x-api-timestamp: Current timestamp in milliseconds (required)

Note: URL query string parameters are not included in the signature. Make sure your signing content contains only the 4 fields above. For this reason, never pass sensitive business parameters via the query string — put them in the signed request body instead.

Steps:

  1. Put the 4 fields above into a contentMap and sort entries by key in ascending order
  2. JSON-serialize the contentMap into a content string
  3. Compute HMAC-SHA256(content, apiSecret)
  4. Base64-encode the HMAC output — that is the signature string

Example:

curl -H 'x-api-key: key' \
     -H 'x-api-timestamp: 1744636844000' \
     -H 'x-api-signature: 0xb7Kxxxx=' \
      'https://{HOST}/path/to/pay' \
      -d '{"data":"test"}'

After sorting and serialization, contentMap looks like this (note: body is a raw string, not an object):

{
  "apiPath": "/path/to/pay",
  "body": "{\"data\":\"test\"}",
  "x-api-key": "key",
  "x-api-timestamp": "1744636844000"
}

HMAC-SHA256 this JSON string with apiSecret and Base64-encode the result to get the signature.

QR Pay Signature Differences

The signature algorithm above applies to Payin / Payout, which pass signature via headers:

  • x-api-key
  • x-api-timestamp
  • x-api-signature

QR Pay and Fusion Pay use a different set of headers and signing mechanism:

  • X-DAPI-API-KEY — API key provided by Paydify
  • X-DAPI-TIMESTAMP — Request timestamp in milliseconds
  • X-DAPI-NONCE — Random number for replay attack prevention
  • X-DAPI-SIGN — Request signature (HMAC-SHA256 lowercase hex)

See Code Implementation · X-DAPI below.

Code Implementation

Payin / Payout Backend Examples

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "net/url"
    "sort"
)

func GenSign(appId, apiSecret, pathUrl, payload string, tm int64) string {
	// Parse URL
	path := ""
	contentMap := make(map[string]string)
	if u, err := url.Parse(pathUrl); err == nil {
		path = u.Path
		for k, v := range u.Query() {
			if len(v) > 0 {
				contentMap[k] = v[0]
			}
		}
	}

	// Build the map to be signed
	contentMap["x-api-key"] = appId
	contentMap["x-api-timestamp"] = fmt.Sprintf("%d", tm) // milliseconds
	contentMap["apiPath"] = path
	contentMap["body"] = payload

	// Get all keys and sort
	keys := make([]string, 0, len(contentMap))
	for k := range contentMap {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// Create ordered map
	orderedMap := make(map[string]string)
	for _, k := range keys {
		orderedMap[k] = contentMap[k]
	}

	// JSON serialization
	content, _ := json.Marshal(orderedMap)

	// Calculate HMAC
	mac := hmac.New(sha256.New, []byte(apiSecret))
	mac.Write(content)
	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

// Usage example
GenSign("A123456", "ABC123", "/path/to/pay?param1=test1&param2=test2", "{"data":"test"}", time.Now().UnixMilli())

QR Pay / Fusion Pay (X-DAPI) Backend Examples

Signing string:

dataToSign = apiKey + timestamp + nonce + queryParamsJson + requestBodyJson
signature  = HMAC-SHA256(dataToSign, secretKey) → lowercase hex string
  • Use queryParamsJson = "{}" when the URL has no query string
  • requestBodyJson must match the wire body, with null-valued fields recursively removed before signing
  • Set headers: X-DAPI-API-KEY, X-DAPI-TIMESTAMP, X-DAPI-NONCE, X-DAPI-SIGN
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/url"
)

// GenQrSign generates X-DAPI-SIGN for QR Pay / Fusion Pay.
// queryParamsJson: JSON string of URL query params; use "{}" when none.
// requestBodyJson: request body JSON after stripping null-valued fields.
func GenQrSign(apiKey, apiSecret, queryParamsJson, requestBodyJson, nonce string, timestamp int64) string {
	if queryParamsJson == "" {
		queryParamsJson = "{}"
	}
	dataToSign := fmt.Sprintf("%s%d%s%s%s", apiKey, timestamp, nonce, queryParamsJson, requestBodyJson)
	mac := hmac.New(sha256.New, []byte(apiSecret))
	mac.Write([]byte(dataToSign))
	return hex.EncodeToString(mac.Sum(nil))
}

// QueryParamsJSON converts URL query params to the JSON string used in signing.
func QueryParamsJSON(rawURL string) (string, error) {
	u, err := url.Parse(rawURL)
	if err != nil {
		return "{}", err
	}
	q := u.Query()
	if len(q) == 0 {
		return "{}", nil
	}
	m := make(map[string]interface{}, len(q))
	for k, vs := range q {
		if len(vs) == 1 {
			m[k] = vs[0]
		} else {
			m[k] = vs
		}
	}
	b, err := json.Marshal(m)
	return string(b), err
}

// StripNullFields recursively removes null-valued fields to match server verification.
func StripNullFields(raw string) (string, error) {
	if raw == "" {
		return "", nil
	}
	var v interface{}
	if err := json.Unmarshal([]byte(raw), &v); err != nil {
		return raw, nil
	}
	cleaned := filterNull(v)
	b, err := json.Marshal(cleaned)
	return string(b), err
}

func filterNull(v interface{}) interface{} {
	switch x := v.(type) {
	case map[string]interface{}:
		m := make(map[string]interface{}, len(x))
		for k, val := range x {
			if val == nil {
				continue
			}
			if filtered := filterNull(val); filtered != nil {
				m[k] = filtered
			}
		}
		return m
	case []interface{}:
		arr := make([]interface{}, 0, len(x))
		for _, item := range x {
			if item == nil {
				continue
			}
			if filtered := filterNull(item); filtered != nil {
				arr = append(arr, filtered)
			}
		}
		return arr
	default:
		return v
	}
}

// Example: POST /qr/v1/scanpay (no query)
func ExampleScanpaySign() (string, error) {
	apiKey := "your_api_key"
	apiSecret := "your_api_secret"
	timestamp := int64(1740000000000)
	nonce := "12345"
	queryParamsJSON := "{}"
	rawBody := `{"codeValue":"pay://example","customerId":"C1","clientIP":"127.0.0.1","extInfo":null}`
	bodyForSign, err := StripNullFields(rawBody)
	if err != nil {
		return "", err
	}
	return GenQrSign(apiKey, apiSecret, queryParamsJSON, bodyForSign, nonce, timestamp), nil
}

Postman Pre-request Script (Payin / Payout)

Paste the following script into the Pre-request Script tab for Payin / Payout:

const appId = 'YOUR_APP_ID';
const apiSecret = 'YOUR_API_SECRET';

const ts = Date.now();

pm.request.headers.upsert({ key: 'x-api-key', value: appId });
pm.request.headers.upsert({ key: 'x-api-timestamp', value: String(ts) });

const path = pm.request.url.getPath();
const rawBody =
  pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';
const bodyForSign = rawBody.replace(/\r?\n/g, '');

const ordered = {
  apiPath: path,
  body: bodyForSign,
  'x-api-key': appId,
  'x-api-timestamp': String(ts),
};

const content = JSON.stringify(ordered);

console.log('Signature content:', ordered);

const signature = CryptoJS.HmacSHA256(content, apiSecret).toString(
  CryptoJS.enc.Base64
);

pm.request.headers.upsert({ key: 'x-api-signature', value: signature });

console.log('Generated signature:', signature);

Postman Pre-request Script (QR Pay / Fusion Pay)

const apiKey = 'YOUR_API_KEY';
const apiSecret = 'YOUR_API_SECRET';

const ts = Date.now();
const nonce = String(Math.floor(Math.random() * 90000) + 10000);

pm.request.headers.upsert({ key: 'X-DAPI-API-KEY', value: apiKey });
pm.request.headers.upsert({ key: 'X-DAPI-TIMESTAMP', value: String(ts) });
pm.request.headers.upsert({ key: 'X-DAPI-NONCE', value: nonce });

const params = {};
pm.request.url.query.each((q) => {
  if (q.disabled) return;
  params[q.key] = q.value;
});
const queryParamsJson =
  Object.keys(params).length === 0 ? '{}' : JSON.stringify(params);

const rawBody =
  pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';
let requestBodyJson = rawBody.replace(/\r?\n/g, '');

const stripNull = (value) => {
  if (value === null || value === undefined) return undefined;
  if (Array.isArray(value)) {
    return value
      .map(stripNull)
      .filter((item) => item !== null && item !== undefined);
  }
  if (typeof value === 'object') {
    const out = {};
    Object.keys(value).forEach((key) => {
      const next = stripNull(value[key]);
      if (next !== null && next !== undefined) out[key] = next;
    });
    return out;
  }
  return value;
};

if (requestBodyJson) {
  try {
    requestBodyJson = JSON.stringify(stripNull(JSON.parse(requestBodyJson)));
  } catch (e) {
    // keep raw body when not valid JSON
  }
}

const dataToSign = apiKey + ts + nonce + queryParamsJson + requestBodyJson;
const signature = CryptoJS.HmacSHA256(dataToSign, apiSecret).toString(
  CryptoJS.enc.Hex
);

pm.request.headers.upsert({ key: 'X-DAPI-SIGN', value: signature });

console.log('QR dataToSign:', dataToSign);
console.log('Generated X-DAPI-SIGN:', signature);

Catalogue