跳至主要内容

API 驗證與簽章

每個請求都必須使用環境專屬憑證,對完整請求內容進行簽章。

必要標頭

Header說明
X-API-KeyFREONE 核發的 API Key
TIMESTAMP毫秒時間戳或 ISO-8601 格式;其原始值會納入簽章
SIGNATUREHMAC-SHA256 結果的小寫十六進位字串

每次送出請求前都應重新產生 TIMESTAMPSIGNATURE,不要重複使用之前請求的值。

保護 Secret

API Secret 不得放在前端程式碼、日誌、版本控制或未授權的通訊內容中。簽章必須在可信任的後端服務產生。

建立簽章內容(Canonical message)

依下列順序以換行字元 \n 串接五個值;即使 query string 或 body 為空,也必須保留該空行。

TIMESTAMP
HTTP_METHOD
REQUEST_PATH
CANONICAL_QUERY
REQUEST_BODY
  • HTTP_METHOD 必須為大寫,例如 GETPOST
  • REQUEST_PATH 只放 API 路徑,例如 /v1/merchants;不要放入 https://api.developer.freone.com?name=value
  • Query 參數名稱按字典順序排列;同名參數值也按字典順序排列後以逗號串接,再以換行串接各組 name=value
  • REQUEST_BODY 必須與實際送出的 body 字串完全相同。

例如請求網址的 Query 是 ?z=2&z=1&empty=CANONICAL_QUERY 應寫成:

empty=
z=1,2

完整請求範例

以下範例皆從環境變數讀取 FREONE_API_KEYFREONE_API_SECRET,並呼叫 Sandbox 的 GET /v1/merchants

import {createHmac} from 'node:crypto';

const apiKey = process.env.FREONE_API_KEY;
const apiSecret = process.env.FREONE_API_SECRET;
if (!apiKey || !apiSecret) throw new Error('Missing FREONE credentials');

const timestamp = Date.now().toString();
const method = 'GET';
const requestPath = '/v1/merchants';
const canonicalMessage = [timestamp, method, requestPath, '', ''].join('\n');
const signature = createHmac('sha256', apiSecret)
.update(canonicalMessage, 'utf8')
.digest('hex');

const response = await fetch(
`https://api.developer-beta.freone.com${requestPath}`,
{
headers: {
'X-API-Key': apiKey,
TIMESTAMP: timestamp,
SIGNATURE: signature,
},
},
);

if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());

常見檢查項目

  • 簽章所用的 HTTP method 是否已轉為大寫。
  • 路徑是否包含 /v1,且沒有帶入網域或 ? 後方的查詢參數。
  • Query 排序與同名參數合併方式是否正確。
  • 簽章使用的 Body 字串,是否與實際送出的內容完全相同。
  • API Key 與 Secret 是否屬於目前呼叫的環境。