Authentication
Wonder API uses AppID and RSA Public Key to authenticate requests. You can generate AppID Pair in Wonder Dashboard.
Need to use AppID and RSA Public Key to generate credential and signature, for more details please refer the following.
When call every OpenAPI, please use the generated credential and signature script to be the prefix operation and generated credential and signature in the header
- Requests will be rejected if the RequestTime in the Credential leave the Wonder server for over than 30 minutes.
- Credential format: $APPID/$REQUEST_DATE/Wonder-RSA-SHA256.
Generate RSA key pair
openSSL is the most popular open source project for cryptography. Follow the openssl command below to generate an rsa key pair.
# generate a private key with 2048 bit key size
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# generate the public key, your should copy-paste the full content when you try to create a appid on wonder-dashboard
openssl rsa -pubout -in private_key.pem -out public_key.pem
The Public Key needs to be uploaded in the Wonder Dashboard to get the Appid, if the private key is lost, you have to regenerate the RSA Key pair and regenerate the Appid.
Please be careful with your private key, no one will have any reason to get your private key.
Credential and signature algorithm
1. Generate credential
Once you have the appid, generate the credential using the following rules:
CREDENTIAL="$APPID/$REQUEST_TIME/Wonder-RSA-SHA256"
$REQUEST_TIME
is UTC time in yyyymmddHHMMSS format:
* yyyy: 4 digests years
* mm: 2 digests months
* dd: 2 digests days
* HH 2 digests hours
* MM 2 digests minutes
* SS 2 digests seconds
When send HTTP request, you need to add header Credential: $CREDENTIAL
Notice: Needs to be generated in UTC time zone, not local time
2. Generate nonce
Nonce
is a random 16-bit alphanumerics.
When send HTTP request, you need to add header Nonce: $Nonce
3. Generate signature
#The Appid generated on wonder dashboard
APPID="${YOUR_APPID}"
# Please refer to generate RSA key pair
SIGNATURE_RSA_PRIVATE_KEY="${YOUR_SIGNATURE_RSA_PRIVATE_KEY}"
REQUEST_TIME="20231201154523" #Format:YYYYMMDDhhmmss, please make sure it's UTC time
#random 16-bit alphanumerics
NONCE="<Randomly Nonce>"
HTTP_URI="$API_URI_PATH" #The full url Path
HTTP_METHOD="$API_HTTP_METHOD" #HTTP Method, GET / POST
BODY = "$API_REQUEST_BODY" #The raw request body
CREDENTIAL="$APPID/$REQUEST_TIME/Wonder-RSA-SHA256"
PRE_SIGNATURE_STRING=HTTP_METHOD + "\n" + HTTP_URI
# If it is a Get request or the request body is empty, then this step is not needed
IF BODY AND LENGTH(BODY) > 0 THEN
PRE_SIGNATURE_STRING = PRE_SIGNATURE_STRING + "\n" + BODY
ENDIF
SIGNATURE = HMAC_SHA256($NONCE,$REQUEST_TIME)
SIGNATURE = HMAC_SHA256($SIGNATURE,"Wonder-RSA-SHA256")
SIGNATURE = HMAC_SHA256($SIGNATURE,$PRE_SIGNATURE_STRING)
HEXED_HASH = HEX($SIGNATURE)
FINAL_SIGNATURE = BASE64_ENCODE(RSA_SHA256_PKCS1v15($SIGNATURE_RSA_PRIVATE_KEY,$HEXED_HASH))
When send HTTP request, you need to add header Signature: $FINAL_SIGNATURE
Verify webhook signature
When you generate the AppID Pair, you can download the Webhook Signature Public Key, which is an RSA key pair managed by Wonder Gateway. Each Webhook will be signed with the RSA private key, and you can verify the legitimacy of the Webhook with the Webhook Signature Public Key you downloaded.
BODY = http_request.request_body
HTTP_METHOD = http_request.method
HTTP_URI = http_request.uri
CREDENTIAL = http_request.headers['Credential']
PARSED_CREDENTIAL = PARSE_CREDENTIAL(CREDENTIAL)
NONCE = http_request.headers['Nonce']
RECEIVED_SIGNATURE = http_request.headers['Signature']
APPID = PARSED_CREDENTIAL['appid']
REQUEST_TIME = PARSED_CREDENTIAL['request_time']
ALGORITHM = PARSED_CREDENTIAL['algorithm']
WEBHOOK_PUBLIC_KEY = "<You will receive the public key when you created appid.>"
PRE_SIGNATURE_STRING = HTTP_METHOD + "\n" + HTTP_URI
# If it is a Get request or the request body is empty, then this step is not needed
IF BODY AND LENGTH(BODY) > 0 THEN
PRE_SIGNATURE_STRING = PRE_SIGNATURE_STRING + "\n" + BODY
ENDIF
SIGNATURE = HMAC_SHA256($NONCE,$REQUEST_TIME)
SIGNATURE = HMAC_SHA256($SIGNATURE,$ALGORITHM)
SIGNATURE = HMAC_SHA256($SIGNATURE,$PRE_SIGNATURE_STRING)
HEXED_HASH = HEX($SIGNATURE)
RSA_SHA256_PKCS1v15_VERIFY($WEBHOOK_PUBLIC_KEY,$HEXED_HASH,$RECEIVED_SIGNATURE)
Example
Please refer to the following algorithmic implementation of the python language, adapted to the technology stack of your choice.
import hashlib
import hmac
import secrets
import string
import requests
import base64
import json
import datetime
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
def generate_random_string(length):
alphabet = string.ascii_letters + string.digits
random_string = ''.join(secrets.choice(alphabet) for _ in range(length))
return random_string
def parse_credential(credential):
output = credential.split("/")
return {
"appid" : output[0],
"request_time" : output[1],
"algorithm" : output[2]
}
def generate_signature_message(credential,nonce,method,uri,body = None):
parsed_credential = parse_credential(credential)
hmac_sha256 = hmac.new(bytes(nonce,'utf-8'),bytes( parsed_credential['request_time'],'utf-8'), hashlib.sha256)
signature_key = hmac_sha256.digest()
hmac_sha256 = hmac.new(signature_key,bytes( parsed_credential['algorithm'],'utf-8'), hashlib.sha256)
signature_key = hmac_sha256.digest()
content = "{}\n{}".format(method,uri)
if body is not None and len(body) > 0:
content += "\n"
content += body
hmac_sha256 = hmac.new(signature_key,bytes(content,'utf-8'), hashlib.sha256)
plain_signature = hmac_sha256.hexdigest()
return plain_signature
def signature(private_key,credential,nonce,method,uri,body = None) :
plain_signature = generate_signature_message(credential,nonce,method,uri,body)
private_key = serialization.load_pem_private_key(
private_key.encode("utf-8"),
password=None,
backend=default_backend()
)
sign = private_key.sign(bytes(plain_signature.encode("utf-8")),padding.PKCS1v15(),hashes.SHA256())
return base64.b64encode(sign)
def verify_signature(webhook_public_key,received_signature,credential,nonce,method,uri,body = None) :
plain_signature = generate_signature_message(credential,nonce,method,uri,body)
print(plain_signature)
public_key = serialization.load_pem_public_key(webhook_public_key.encode('utf-8'),backend=default_backend())
received_signature_bytes = base64.b64decode(received_signature)
return public_key.verify(
received_signature_bytes,
plain_signature.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256()
)
def test_signature():
signature_private_key = '''
-----BEGIN PRIVATE KEY-----
YOUR PRIVATE KEY CONTENT
-----END PRIVATE KEY-----
'''
http_method = "POST"
domain = "https://gateway-stg.wonder.today"
request_uri = "/svc/payment/api/v1/openapi/orders?with_payment_link=true"
body = {
"order": {
"charge_fee": "10.00",
"tips": "0.00",
"due_date": "2024-06-09",
"currency": "HKD",
"reference_number": "<your side unique order number>",
"note": "test",
"subtotal": "10",
"line_items": [
{
"purchasable_type": "Charge",
"purchase_id": "0",
"price": "10",
"quantity": "1",
"total": "10"
}
]
}
}
request_body = json.dumps(body)
current_time_utc = datetime.datetime.utcnow()
request_time = current_time_utc.strftime("%Y%m%d%H%M%S")
appid = "<your appid>"
credential = "{}/{}/{}".format(appid,request_time,"Wonder-RSA-SHA256")
nonce = generate_random_string(16)
hexed_signature = signature(signature_private_key,credential,nonce,http_method,request_uri,request_body)
resp = requests.post(domain + request_uri,data=request_body,headers={
"Content-Type" : "application/json",
"Signature" : hexed_signature,
"Credential" : credential,
"Nonce" : nonce,
})
print(resp.json())
def test_verify():
webhook_public_key = '''-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnD3DTS2JLDndYZLFcYAR
CltWblzNUY0t+Odrz09uUJT1+/a7LR7OWOOONWoCq+ppcq+TXZ1Ix/X4DLKt03JM
IP9pshxULzvMF/M6UlAGbEx5yrC/+rRrg1xUFqOiTdcdFxEnULbS/KRUZ6Sp9CvG
HNNYIN/5wk4Xt38ytyWXXfiDuFsgn/eAjS00y+1rLt33M08qKC/5rwNJ9eqwqLsP
ZQ9DhLg8eVgPIOeZzXT1PecHFrj2vCHbEOLxyCbw8hM4X+nl5VQNLEhTNy1GVb+H
ZUYEVGntexNyW/0oO1mKSiXNpzwTPMaSD6gBpKPL3gBMRiLopwWXm3zebmWT3a/k
ywIDAQAB
-----END PUBLIC KEY-----'''
uri = "/webhook"
method = "POST"
credential = "22ce3f16-27da-4130-a23c-c4eef8dc25f5/20240515095201/Wonder-RSA-SHA256"
nonce = "2piC3Ndq1tjvWLRS"
received_signature = "lM42cgyuLS98Dieydc8K2OD3KwYkOXibpV9pFvr/R0i/830M/FPKUKbav2UBBN3M3EdPk/PpvKQlvBNT+NbEg20CKuiDTZWDc3r7KiA1pdZsui/57XCVhC2s01W8jEM+G5lS362+p8+E0K6UKQDrJMyVpbDT31XSkSJIxae+uDi2nJr4DnIkemeU2LlNDRPPGe9NeX7z3B3N3LwIiQgKMyauPqAjro0UrZykQM9pv4UySRSU2cT8EcjQmyKxbzyuR2A47PyeodJvotlIthdfCHIxG52D06tpRJlRVbUdvxSg14bFiPbr3FwCvruZlbR15gOanJCqE4wp4fC8qEXXsg=="
body = '{"source":"Wonder Paystation Link","type":"Invoice","number":"202405151752014702131723","due_date":null,"paid_total":"0","unpaid_total":"0.1","reference_number":"1000005","initial_total":0.1,"initial_tips":0,"subtotal":0.1,"state":"invoiced","correspondence_state":"unpaid","currency":"HKD","note":"test and use wechat pay","auth_code":"jAHPhwKLKHBNXnR","return_url":"http://wwww.baidu.com","notify_url":"http://47.99.157.36:50355/webhook","line_items":[{"label":"测试商品1","purchasable_type":"Charge","purchase_id":null,"price":0.1,"quantity":1,"total":0.1,"uuid":"cd8fa487-19c7-4203-9931-f7382a5c0fdf","created_at":"2024-05-15T09:52:01.200745014Z","updated_at":"2024-05-15T09:52:01.200745014Z"}],"transactions":[],"created_at":"2024-05-15T09:52:01.197056873Z","updated_at":"2024-05-15T09:52:01.197056873Z"}'
verify_signature(webhook_public_key,received_signature,credential,nonce,method,uri,body)
test_verify()
Online Signature Debug Tool
Through the Wonder Gateway Online Signature Debug Tool you can quickly online debugging signature and webhook verification of each detailed step, we recommend that you through this tool for development debugging.