Pinnacle EmblemPinnacle API 文檔
Pinnacle Advisoryv1.4.2 ProductionBVI Licensed VG1110

狀態通知 (Webhooks)

非同步計算任務完成、架構審計通過與風險告警的實時 Webhook 回調機制。

針對長時間非同步計算任務(如大數據集分析、企業架構深度評審),Pinnacle 平台透過 Webhook 機制主動向訂閱端點推送即時事件,避免高頻輪詢。

核心 Webhook 事件清單

事件類型 (Event)觸發時機負載主要內容 (Payload)
analytics.job.completed非同步分析運算任務正常完成job_id, result_summary, execution_duration_ms, metrics
analytics.job.failed非同步運算過程中出現模型未收斂或超時job_id, error_code, error_message, retryable
architecture.audit.passed企業架構評審通過且未發現嚴重阻斷項assessment_id, health_score, tech_debt_hours
risk.threshold.exceeded實時風險指標超出預設高危閾值indicator_code, current_value, threshold_value, severity
governance.entity.updated離岸實體股權穿透或簽字人變更完成entity_id, updated_by, registry_hash

Webhook 簽名驗證安全機制

為確保回調請求確實源自 Pinnacle 伺服器且未遭中間人竄改,每個 Webhook 請求均攜帶 `X-Pinnacle-Webhook-Signature` 標頭:

Webhook 簽名驗證規範
Signature = HMAC_SHA256_HEX(WEBHOOK_SECRET, TIMESTAMP + "." + RAW_REQUEST_BODY)
Header: X-Pinnacle-Webhook-Signature: t=1774944000,v1=9f8e7d6c5b4a...

多語言 Webhook 接收與驗簽服務代碼

在您的伺服器端部署接收器時,務必在解析 JSON 前提取原始 Raw Body 進行驗簽:

接收並驗證 Webhook 回調

from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib

app = FastAPI()
WEBHOOK_SECRET = "whsec_your_registered_secret_key"

@app.post("/api/pinnacle-webhook")
async def handle_webhook(request: Request):
    sig_header = request.headers.get("X-Pinnacle-Webhook-Signature")
    if not sig_header:
        raise HTTPException(status_code=400, detail="Missing signature")

    # 格式: t=1774944000,v1=abcdef...
    parts = dict(p.split("=") for p in sig_header.split(","))
    t, v1 = parts.get("t"), parts.get("v1")

    raw_body = await request.body()
    data_to_verify = f"{t}.{raw_body.decode('utf-8')}"
    expected_sig = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        data_to_verify.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(v1, expected_sig):
        raise HTTPException(status_code=401, detail="Invalid signature")

    payload = await request.json()
    print("Received Event:", payload.get("event"), payload.get("data"))
    return {"status": "accepted"}

On this page