웹훅에 시크릿이 있으면 모든 요청은 타임스탬프 헤더와 원본 본문에 대한 HMAC-SHA256으로 서명됩니다.
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가 두 시크릿으로 모두 서명하는 기간은 없으므로, 전환하는 동안 수신 측에서 이전 시크릿과 새 시크릿을 모두 허용하세요.