API Signature & Verification Instructions
Scope: All public API endpoints that go through the VerifyApiSign middleware.
Code references:
app/Http/Middleware/VerifyApiSign.phpgetSignByYabandPayV3()inapp/Helpers/functions.php
1. Overall Verification Flow
After the server receives a request, validation runs in the following order:
- Check the request header for
Authorization: Bearer {token}. - Check that the request body contains
uid,sign,timestamp,nonce, anddata. - Look up the API account by
uid. - Verify that the
tokenin the header matches the token bound to that API account. - If an IP whitelist is configured for the account, validate the source IP.
- Recompute the signature using the account's
secret_key. - Compare the server-side computed signature with the
signvalue in the request (strict equality). - Verify that the current API path is included in the account's allowed endpoint permissions.
flowchart TD
A[Client sends request] --> B[Check Authorization Bearer Token]
B --> C[Check uid sign timestamp nonce data existence]
C --> D[Lookup API account by uid]
D --> E[Validate token match]
E --> F[Validate source IP whitelist]
F --> G[Recompute signature with secret_key]
G --> H{sign matches?}
H -- Yes --> I[Validate endpoint permission]
I --> J[Allow business logic to run]
H -- No --> K[Return -1001]
2. Request Format
2.1 Header
Authorization: Bearer {api_account_token}
Content-Type: application/json
2.2 Body
{
"uid": "8000000001",
"timestamp": 1731926400,
"nonce": "Qe3CkH2xP8",
"sign": "26ebd4b01363160622c8d515fb2afa76b81ea529b2f2a5bb8d6fbf53c1b3d645",
"data": {
"request_id": "req_202411180049",
"customer_id": "CUST20260001",
"timezone": "Asia/Shanghai",
"customer_type": "legal_entity",
"holder_name": "Yabandpay B.V.",
"alternative_name": "YB HQ",
"creditor_identifier": "YABAND001",
"access": 1,
"status": 1,
"remark": "NL account opening"
}
}
2.3 Field Descriptions
| Field | Type | Required | Description |
|---|---|---|---|
| uid | string | Yes | API account unique id. Current rule is a 10-digit number starting with 8. |
| timestamp | integer | Yes | Unix timestamp in seconds |
| nonce | string | Yes | Random string. Recommended to be unique per request. |
| sign | string | Yes | HMAC-SHA256 signature computed by the rules below, as a 64-character lowercase hex string |
| data | object | Yes | Business parameter object; all fields inside participate in signing |
| role | string | No | Even if provided, it is not included in signing |
3. Signature Rules
3.1 TL;DR
This project uses digital signature (digest signing), not reversible encryption.
Signature algorithm is fixed as:
HMAC-SHA256
Secret key is fixed as:
secret_key
3.2 Calculation Steps
Clients must compute sign strictly in the following order:
- Prepare the complete request body.
- Remove the
signfield. - If a
rolefield exists, remove it because it is excluded from signing. - Take all fields inside the
dataobject and flatten them to the top level. - Add the top-level fields
uid,timestamp, andnonceinto the same to-be-signed set. - Sort all fields by key in ascending order.
- Concatenate as
key=valuepairs. - Join all key/value pairs with
&into a single string. - Compute
HMAC-SHA256over that string usingsecret_keyas the secret. - Use the resulting 64-character lowercase hex string as the final
sign.
3.3 Actual Fields Participating in Signing
Using the sample request above, the fields that actually participate in signing are:
| Sorted key | Value |
|---|---|
| access | 1 |
| alternative_name | YB HQ |
| creditor_identifier | YABAND001 |
| customer_id | CUST20260001 |
| customer_type | legal_entity |
| holder_name | Yabandpay B.V. |
| nonce | Qe3CkH2xP8 |
| remark | NL account opening |
| request_id | req_202411180049 |
| status | 1 |
| timestamp | 1731926400 |
| timezone | Asia/Shanghai |
| uid | 8000000001 |
3.4 Raw String to be Signed
The following string is the exact signing source string used by the server:
access=1&alternative_name=YB HQ&creditor_identifier=YABAND001&customer_id=CUST20260001&customer_type=legal_entity&holder_name=Yabandpay B.V.&nonce=Qe3CkH2xP8&remark=NL account opening&request_id=req_202411180049&status=1×tamp=1731926400&timezone=Asia/Shanghai&uid=8000000001
3.5 Demo Secret and Signature Output
For debugging convenience, here is a verifiable sample dataset:
secret_key = demo_secret_key_123456
After running HMAC-SHA256 on the signing string above, the signature is:
26ebd4b01363160622c8d515fb2afa76b81ea529b2f2a5bb8d6fbf53c1b3d645
Which means the sign value in the following body is exactly consistent with the actual implementation:
{
"uid": "8000000001",
"timestamp": 1731926400,
"nonce": "Qe3CkH2xP8",
"sign": "26ebd4b01363160622c8d515fb2afa76b81ea529b2f2a5bb8d6fbf53c1b3d645",
"data": {
"request_id": "req_202411180049",
"customer_id": "CUST20260001",
"timezone": "Asia/Shanghai",
"customer_type": "legal_entity",
"holder_name": "Yabandpay B.V.",
"alternative_name": "YB HQ",
"creditor_identifier": "YABAND001",
"access": 1,
"status": 1,
"remark": "NL account opening"
}
}
4. Code Examples
4.1 PHP Example
<?php
$secretKey = 'demo_secret_key_123456';
$body = [
'uid' => '8000000001',
'timestamp' => 1731926400,
'nonce' => 'Qe3CkH2xP8',
'data' => [
'request_id' => 'req_202411180049',
'customer_id' => 'CUST20260001',
'timezone' => 'Asia/Shanghai',
'customer_type' => 'legal_entity',
'holder_name' => 'Yabandpay B.V.',
'alternative_name' => 'YB HQ',
'creditor_identifier' => 'YABAND001',
'access' => 1,
'status' => 1,
'remark' => 'NL account opening',
],
];
function buildSign(array $param, string $secretKey): string
{
unset($param['sign']);
unset($param['role']);
$tmp = [];
$data = $param['data'] ?? [];
unset($param['data']);
foreach ($data as $k => $v) {
$tmp[$k] = $v;
}
foreach ($param as $k => $v) {
$tmp[$k] = $v;
}
ksort($tmp);
$pairs = [];
foreach ($tmp as $k => $v) {
$pairs[] = $k . '=' . $v;
}
return hash_hmac('sha256', implode('&', $pairs), $secretKey);
}
$body['sign'] = buildSign($body, $secretKey);
echo $body['sign'];
// Output:
// 26ebd4b01363160622c8d515fb2afa76b81ea529b2f2a5bb8d6fbf53c1b3d645
4.2 cURL Example
curl --location 'https://ibantest.yabandpay.com/api/iban/add' \
--header 'Authorization: Bearer demo_token_123456' \
--header 'Content-Type: application/json' \
--data '{
"uid": "8000000001",
"timestamp": 1731926400,
"nonce": "Qe3CkH2xP8",
"sign": "26ebd4b01363160622c8d515fb2afa76b81ea529b2f2a5bb8d6fbf53c1b3d645",
"data": {
"request_id": "req_202411180049",
"customer_id": "CUST20260001",
"timezone": "Asia/Shanghai",
"customer_type": "legal_entity",
"holder_name": "Yabandpay B.V.",
"alternative_name": "YB HQ",
"creditor_identifier": "YABAND001",
"access": 1,
"status": 1,
"remark": "NL account opening"
}
}'
5. Server Response Codes
| code | Description |
|---|---|
| 401 | Missing Authorization, wrong Bearer format, or missing uid/sign/timestamp/nonce/data |
| -1001 | Signature error |
| -2001 | The account for the given uid does not exist or was not found in the account cache |
| -2010 | The current account does not have permission to call this API |
| -3036 | Source IP is not in the whitelist |
| -3100 | The token in the header does not match the API account for the given uid |
6. Integration Notes
signmust be a lowercase, 64-character hex string.- Fields inside
dataare flattened to the top level before sorting and signing. The signature is not over the JSON structure as-is. - Even if
roleis sent, it is not included in signing. - Currently the middleware only validates that
timestampandnonceexist. Strict timeout/replay protection has not been enabled yet. - The current sign function concatenates values as plain strings, so it is recommended to only use scalar values inside
data. Avoid passing nested arrays/objects directly. Authorizationin the header anduidin the body must belong to the same API account. Otherwise-3100is returned.- If the account has an IP whitelist configured, requests must originate from a whitelisted IP.
7. Self-Test Cases for Integration
Here are 10 common input scenarios and expected results during integration debugging:
| # | Input | Expected result |
|---|---|---|
| 1 | Headers, body and signature are all correct, and the endpoint permission is granted | Pass middleware successfully |
| 2 | Authorization header is missing |
Return 401 with authentication missing prompt |
| 3 | Authorization is not in Bearer xxx format |
Return 401 with token format error |
| 4 | Token in header is empty | Return 401 with invalid token |
| 5 | Body is missing uid |
Return 401 |
| 6 | Body is missing sign |
Return 401 |
| 7 | data in body is an empty array or empty |
Return 401 |
| 8 | The uid account exists but the header token is not this account's token |
Return -3100 |
| 9 | Account has IP whitelist configured but the source IP is not whitelisted | Return -3036 |
| 10 | Any signed field is modified but sign is not recomputed |
Return -1001 |
8. Debugging Recommendations
Clients are strongly recommended to log the following 4 items for every request. Debugging will become much faster:
- Raw request body
- Fields after sorting (ready for signing)
- The final signing source string
- The computed
sign
If debugging fails, both sides only need to compare whether the signing source strings match. Usually the root cause can be identified within minutes.