API Signature & Verification Instructions

Scope: All public API endpoints that go through the VerifyApiSign middleware.

Code references:

  • app/Http/Middleware/VerifyApiSign.php
  • getSignByYabandPayV3() in app/Helpers/functions.php

1. Overall Verification Flow

After the server receives a request, validation runs in the following order:

  1. Check the request header for Authorization: Bearer {token}.
  2. Check that the request body contains uid, sign, timestamp, nonce, and data.
  3. Look up the API account by uid.
  4. Verify that the token in the header matches the token bound to that API account.
  5. If an IP whitelist is configured for the account, validate the source IP.
  6. Recompute the signature using the account's secret_key.
  7. Compare the server-side computed signature with the sign value in the request (strict equality).
  8. 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:

  1. Prepare the complete request body.
  2. Remove the sign field.
  3. If a role field exists, remove it because it is excluded from signing.
  4. Take all fields inside the data object and flatten them to the top level.
  5. Add the top-level fields uid, timestamp, and nonce into the same to-be-signed set.
  6. Sort all fields by key in ascending order.
  7. Concatenate as key=value pairs.
  8. Join all key/value pairs with & into a single string.
  9. Compute HMAC-SHA256 over that string using secret_key as the secret.
  10. 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&timestamp=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

  1. sign must be a lowercase, 64-character hex string.
  2. Fields inside data are flattened to the top level before sorting and signing. The signature is not over the JSON structure as-is.
  3. Even if role is sent, it is not included in signing.
  4. Currently the middleware only validates that timestamp and nonce exist. Strict timeout/replay protection has not been enabled yet.
  5. 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.
  6. Authorization in the header and uid in the body must belong to the same API account. Otherwise -3100 is returned.
  7. 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:

  1. Raw request body
  2. Fields after sorting (ready for signing)
  3. The final signing source string
  4. 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.

results matching ""

    No results matching ""