Narwhal Webhook 接收設定

用您的 HTTPS URL 以及所需的國家和系列建立 Webhook 目的地,然後在信任每次投遞之前先驗證其簽章。每個帳戶最多可保留 25 個目的地,每次投遞計為一次呼叫。

建立目的地並接收投遞

Narwhal 的各資料 API 共用一個帳戶和一把 API 金鑰,REST 與 MCP 也共用該金鑰。請把金鑰存放在 NARWHAL_API_KEY 環境變數中,並以 Authorization: Bearer YOUR_KEY 的形式發送到 https://api.narwhalapi.com。

  1. 建立目的地 將 HTTPS URL、國家代碼和資料族 slug 發送到 POST /v1/webhooks/destinations。URL 最多 2,048 個字元;選擇 1 到 60 個 alpha-3 國家代碼或 WLD,以及 1 到 14 個資料族 slug,其中包括 holidays、predictions 和 filings。請求中也可以帶上 enabled: true。該 curl 請求為 prices 和 labour 資料族選擇了 USA 和 MEX。請把範例中的 HTTPS URL 換成你的接收端接受投遞的 URL。
    建立 Webhook 目的地
    curl -s -X POST https://api.narwhalapi.com/v1/webhooks/destinations \
      -H "Authorization: Bearer $NARWHAL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url":"https://example.com/narwhal",
           "countries":["USA","MEX"],"families":["prices","labour"]}'
  2. 保存目的地的金鑰 回應包含 destination_id 和 secret,其中 secret 只顯示一次。請把 NARWHAL_WEBHOOK_SECRET 設定為這個金鑰,供接收端使用。請保留目的地 ID,用於指向該目的地的請求,包括測試投遞請求。計算投遞簽章時使用傳回的金鑰;API 金鑰則繼續放在 API 請求的 authorization 標頭中。rotate-secret 傳回的金鑰同樣只顯示一次;輪替後的 24 小時內,簽章標頭會再帶上一個用舊金鑰簽署的值。
  3. 處理前先驗證簽章 下面的接收端以恆定時間比較簽章,並拒絕早於五分鐘的時間戳記。只要有一個簽章值相符,就接受該投遞。Python SDK 也提供了驗證函式:from narwhalapi.webhooks import verify。請盡快傳回 2xx,再去做後續處理。
    Python(Flask):驗證簽章的接收端
    import hashlib, hmac, json, os, time
    from flask import Flask, abort, request
    
    app = Flask(__name__)
    SECRET = os.environ[
        "NARWHAL_WEBHOOK_SECRET"
    ]  # returned once when you create the destination
    seen = set()  # use a database table in production
    
    
    @app.post("/narwhal")
    def narwhal():
        raw = request.get_data()  # exact bytes, before any JSON parsing
        stamp = request.headers.get("x-narwhal-timestamp", "")
        if not stamp.isdigit() or abs(time.time() - int(stamp)) > 300:
            abort(400)  # missing, or older than five minutes
        expected = (
            hmac.new(SECRET.encode(), f"{stamp}.".encode() + raw, hashlib.sha256)
            .hexdigest()
            .encode()
        )
        header = request.headers.get("x-narwhal-signature", "")
        sent = [part.strip().removeprefix("sha256=").encode() for part in header.split(",")]
        if not any(hmac.compare_digest(expected, candidate) for candidate in sent):
            abort(401)  # a missing or malformed header lands here, not in a 500
        try:
            event = json.loads(raw)
        except ValueError:
            abort(400)
        if not isinstance(event, dict):
            abort(400)
        if event.get("event_id") in seen:  # retries and replays repeat the same event_id
            return "", 200
        seen.add(event.get("event_id"))
        print(event.get("type"), event.get("country"), event.get("family"))
        return "", 200
  4. 請求測試投遞 使用已儲存的目的地 ID 呼叫 POST /v1/webhooks/destinations/{destination_id}/test。測試投遞帶有 is_test: true,沒有游標。投遞狀態為 pending、leased、succeeded、failed 和 cancelled。建立超出上限的目的地會回傳 409 limit_reached。

事件類型包括 release.scheduled、release.rescheduled、release.cancelled、release.updated;dataset.published 和 dataset.revised;yield_curve.published 和 yield_curve.revised;以及 holiday.calendar.revised。預測類有 prediction.event.closed 和 prediction.event.settled。申報文件類有 filing.published。處理事件時請使用酬載中的 type、country 和 family 欄位。

當時間戳記遺失或無效,以及請求本文無法解析為 JSON 物件時,接收端傳回 400。簽章遺失或格式錯誤時傳回 401。簽章比較通過後,接收端先解析請求本文,再讀取事件欄位並記錄事件 ID。

接收所關注公司的新申報文件

將 filings 系列和 companies 清單加入目的地。此後,這些公司每提交一次文件,Narwhal 就會傳送一個 filing.published 事件。對於 13D 或 13G 持股申報,當所列公司是被持股的公司時,也會傳送事件。每個帳戶最多可關注 1,000 家公司。

監控 Apple 的申報文件
curl -s -X POST https://api.narwhalapi.com/v1/webhooks/destinations \
  -H "Authorization: Bearer $NARWHAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/narwhal","countries":["USA"],
       "families":["filings"],"companies":["usa:0000320193"]}'

公司透過 Filings API 的 company_id 指定,例如 Apple 為 usa:0000320193。filings 資料族必須帶 companies 清單,缺少時會被拒絕。未知公司會傳回 400,並列出找不到的 ID。請求仍需要 countries,但國家並不會縮小申報文件的範圍。

申報文件事件酬載
{
  "schema_version": "1",
  "event_id": "...",
  "cursor": "1",
  "type": "filing.published",
  "country": "USA",
  "recorded_at": "2026-10-30T21:05:12Z",
  "filing": {
    "filing_id": "0000320193-26-000106",
    "company_id": "usa:0000320193",
    "form": "10-K",
    "type": "annual",
    "fy": 2026,
    "fp": "FY",
    "period_end": "2026-09-26",
    "filed_at": "2026-10-30T21:01:40Z"
  }
}

用 filing.filing_id 從 Filings API 取得該申報文件。當申報文件沒有 fy、fp 和 period_end 時(多數事件類和持股類申報即是如此),這些欄位會被省略。

不寫程式接收 Webhook

Zapier 的 Catch Hook、Make 的 Custom webhook 或 n8n 的 Webhook 節點都可以提供目的地 URL。把該 URL 填入目的地的 url 欄位,並為流程選擇國家和資料族。請保密 catch hook 的 URL:這些無程式碼目的地不會替你驗證 Narwhal 簽章。

簽章驗證需要程式碼,例如上面的 Python 接收端。如果需要驗證,請用該接收端處理傳入的投遞。Webhook 參考介紹了目的地、投遞歷史和重送操作;事件參考介紹了可從游標開始讀取事件的 REST 日誌。

選擇接收端還是輪詢

情況最適合的方式原因
你自己的服務Python 接收端驗證簽章並處理重複的事件 ID
無程式碼流程Zapier、Make 或 n8n 的 catch hook提供目的地 URL;請保密該 URL
用輪詢代替投遞GET /v1/events 或經濟行事曆日誌依游標讀取;行事曆依日期涵蓋

為什麼要驗證原始 Webhook 請求本文?

簽章針對的是請求本文的精確位元組,因此解析後再重新序列化會改變你所比較的內容。把 x-narwhal-timestamp 當作 Unix 秒讀取,然後依序串接它的值、一個英文句點和原始請求本文。用以 UTF-8 編碼的金鑰計算小寫十六進位的 HMAC-SHA256。

x-narwhal-signature 標頭的值為 sha256=<hex>。與接收端的做法一樣,請在解析 JSON 之前,以恆定時間將計算出的值與收到的簽章進行比較。

如果我的 Webhook 伺服器停機了怎麼辦?

投遞會重試,投遞歷史可讓你查看失敗並重送某次投遞。用 GET .../deliveries 查看目的地的歷史,再對想重新接收的投遞呼叫 POST .../deliveries/{delivery_id}/replay。重送會重複同一個事件。

若要從儲存的位置繼續追趕,請從最後一個游標開始讀取 GET /v1/events。請為日誌這條路徑保存好游標;測試投遞操作不會提供游標。

同一個事件會收到兩次嗎?

會。重試和重送會重複相同的 event_id。請把它當作冪等鍵,並在處理事件之前先儲存。在正式環境中,如接收端註解所示,請用資料庫資料表保存這些 ID。

範例接收端把 ID 保存在 seen 中。ID 已存在時,它直接傳回 200,不會再次印出該事件。遇到未見過的 ID 時,它先加入該 ID,再印出事件的 type、country 和 family。

範例:接收一次投遞

對於記錄於 2026-09-25T15:44:56Z 的事件,酬載標明了 dataset.published、國家 MEX 和資料族 labour,還帶有結構描述版本、事件 ID、游標、資源 ID 和資源版本。

經濟資料事件酬載
{
  "schema_version": "1",
  "event_id": "...",
  "cursor": "1",
  "type": "dataset.published",
  "country": "MEX",
  "family": "labour",
  "resource_id": "...",
  "resource_version": "...",
  "recorded_at": "2026-09-25T15:44:56Z"
}

投遞會帶著時間戳記和簽章標頭到達。接收端讀取時間戳記,要求其時長不足 300 秒,然後重新計算 <timestamp>.<body> 的 HMAC。簽章相符後,它解析酬載並查找 event_id。對於未見過的事件,它儲存該 ID,印出下面的欄位,然後傳回 200。

接收端輸出
dataset.published MEX labour

發布於 。

後續步驟