X-Mateflow-Signature = "sha256=" + hex(
HMAC-SHA256(secret, X-Mateflow-Timestamp + "." + raw_request_body)
)
- 生のバイト列を使ってください。本文では HTML で特殊な文字がエスケープされることがあるため、JSON をパースして再びシリアライズするとバイト列が変わり、署名が一致しません。
- タイムスタンプは本文ではなくヘッダーから取ってください。手動再送で更新されるのは、ヘッダーのタイムスタンプと署名だけです。
- タイムスタンプの検査はリトライの妨げになりません。自動リトライも手動再送も、そのつど署名し直されるため、5 分のウィンドウで十分です。
const crypto = require('crypto');
// rawBody must be the untouched bytes, e.g. from express.raw({ type: 'application/json' }).
function verifyMateflowSignature(rawBody, headers, secret, toleranceSeconds = 300) {
const timestamp = headers['x-mateflow-timestamp'];
const received = headers['x-mateflow-signature'] || '';
if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) {
return false;
}
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest('hex');
const a = Buffer.from(received);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hashlib
import hmac
import time
def verify_mateflow_signature(raw_body: bytes, headers, secret: str, tolerance_seconds: int = 300) -> bool:
timestamp = headers.get("X-Mateflow-Timestamp", "")
received = headers.get("X-Mateflow-Signature", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance_seconds:
return False
signed = timestamp.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(received.encode(), expected.encode())テストベクター
署名の計算が正しいかどうかの確認に、これらの値を使ってください。タイムスタンプは期限切れなので、テスト中は時間ウィンドウを緩めてください。
secret my_signing_secret
X-Mateflow-Timestamp 1793664000
raw body (UTF-8) {"event":"member.joined","data":{"username":"张三"}}
X-Mateflow-Signature sha256=3f4c83828ce0086150c9bd48c1654899357b7c05265c1d1b6c172225ee04b4b4シークレットのローテーション
新しいシークレットは保存した時点で有効になり、キューに入っているリトライもそれで署名されます。Mateflow が両方で署名する期間はないため、切り替え中は受信側で古いシークレットと新しいシークレットの両方を受け付けるようにしてください。