Signature Algorithm
Algorithm Description
Fields participating in the signature:
apiPath: Request URI path (required), excluding host, query string, and the/crypto-paymentprefix, e.g./payin/v1/createPaymentbody: Raw JSON string of the request body (required), byte-for-byte identical to what is sent to Paydifyx-api-key: Same asappId(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
bodyinstead.
Steps:
- Put the 4 fields above into a
contentMapand sort entries bykeyin ascending order - JSON-serialize the
contentMapinto acontentstring - Compute
HMAC-SHA256(content, apiSecret) - 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.
Code Implementation
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¶m2=test2", "{"data":"test"}", time.Now().UnixMilli())Postman Pre-request Script
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);
QR Pay and Fusion Pay use the
X-DAPI-*header family with a different signing rule. See QR Pay Signature Algorithm.