用您的 HTTPS URL 以及所需的國家和系列建立 Webhook 目的地,然後在信任每次投遞之前先驗證其簽章。每個帳戶最多可保留 25 個目的地,每次投遞計為一次呼叫。
建立目的地並接收投遞
Narwhal 的各資料 API 共用一個帳戶和一把 API 金鑰,REST 與 MCP 也共用該金鑰。請把金鑰存放在 NARWHAL_API_KEY 環境變數中,並以 Authorization: Bearer YOUR_KEY 的形式發送到 https://api.narwhalapi.com。
- 建立目的地 將 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。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"]}' - 保存目的地的金鑰 回應包含
destination_id和secret,其中secret只顯示一次。請把NARWHAL_WEBHOOK_SECRET設定為這個金鑰,供接收端使用。請保留目的地 ID,用於指向該目的地的請求,包括測試投遞請求。計算投遞簽章時使用傳回的金鑰;API 金鑰則繼續放在 API 請求的 authorization 標頭中。rotate-secret傳回的金鑰同樣只顯示一次;輪替後的 24 小時內,簽章標頭會再帶上一個用舊金鑰簽署的值。 - 處理前先驗證簽章 下面的接收端以恆定時間比較簽章,並拒絕早於五分鐘的時間戳記。只要有一個簽章值相符,就接受該投遞。Python SDK 也提供了驗證函式:
from narwhalapi.webhooks import verify。請盡快傳回 2xx,再去做後續處理。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 - 請求測試投遞 使用已儲存的目的地 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 家公司。
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發布於 。