教學課程:使用 Lambda 函式 URL 建立 Webhook 端點 - AWS Lambda

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

教學課程:使用 Lambda 函式 URL 建立 Webhook 端點

在本教學課程中,您將建立 Lambda 函式 URL 來實作 Webhook 端點。Webhook 是一種輕量型的事件驅動型通訊機制,能透過 HTTP 自動在應用程式間傳輸資料。您可以使用 Webhook 來接收有關其他系統中發生事件的即時更新,例如當新客戶在網站上註冊、處理付款或上傳檔案時。

使用 Lambda 時,可以透過 Lambda 函式 URL 或 API Gateway 來實作 Webhook。對於不需要進階授權或請求驗證等功能的簡單 Webhook,函式 URL 是很好的選擇。

提示

如果您不確定哪種解決方案最適合特定使用案例,請參閱選取一種使用 HTTP 請求調用 Lambda 函數的方法

先決條件

要完成本教學課程,必須在本機電腦上安裝 Python (3.8 版或更新版本) 或 Node.js (18 版或更新版本)。

為了使用 HTTP 請求測試端點,本教學課程使用了 curl。這是一個命令列工具,能透過多種網路通訊協定來傳輸資料。若尚未安裝此工具,請參閱 curl 文件了解安裝方法。

建立 Lambda 函式

首先建立當 HTTP 請求傳送至 Webhook 端點時執行的 Lambda 函式。在此範例中,傳送端應用程式會在付款提交時傳送更新,並在 HTTP 請求內文中指出付款是否成功。Lambda 函式會剖析請求,再依據付款狀態採取動作。在此範例中,程式碼僅會列印付款的訂單 ID,但在實際應用程式中,您可以將訂單新增至資料庫或傳送通知。

函式還實作了 Webhook 最常用的身分驗證方法,即雜湊訊息驗證碼 (HMAC)。使用此方法時,傳送端與接收端應用程式皆會共用一個秘密金鑰。傳送端應用程式使用雜湊演算法,利用此金鑰和訊息內容產生唯一簽章,並將該簽章作為 HTTP 標頭包含在 Webhook 請求中。然後,接收端應用程式會重複此步驟,使用秘密金鑰產生簽章,並將結果值與請求標頭中傳送的簽章進行比較。如果結果相符,請求會視為合法。

使用 Lambda 主控台並選擇 Python 或 Node.js 執行時期來建立函式。

Python
建立 Lambda 函式
  1. 開啟 Lambda 主控台中的函數頁面

  2. 執行下列動作,建立基本的 'Hello world' 函式:

    1. 選擇 Create function (建立函數)

    2. 選取從頭開始撰寫

    3. 針對函數名稱,請輸入 myLambdaWebhook

    4. 針對執行期,選取 python3.14

    5. 選擇 Create function (建立函數)

  3. 程式碼來源窗格中,複製並貼上以下項目來取代現有程式碼:

    import json import hmac import hashlib import os def lambda_handler(event, context): # Get the webhook secret from environment variables webhook_secret = os.environ['WEBHOOK_SECRET'] # Verify the webhook signature if not verify_signature(event, webhook_secret): return { 'statusCode': 401, 'body': json.dumps({'error': 'Invalid signature'}) } try: # Parse the webhook payload payload = json.loads(event['body']) # Handle different event types event_type = payload.get('type') if event_type == 'payment.success': # Handle successful payment order_id = payload.get('orderId') print(f"Processing successful payment for order {order_id}") # Add your business logic here # For example, update database, send notifications, etc. elif event_type == 'payment.failed': # Handle failed payment order_id = payload.get('orderId') print(f"Processing failed payment for order {order_id}") # Add your business logic here else: print(f"Received unhandled event type: {event_type}") # Return success response return { 'statusCode': 200, 'body': json.dumps({'received': True}) } except json.JSONDecodeError: return { 'statusCode': 400, 'body': json.dumps({'error': 'Invalid JSON payload'}) } except Exception as e: print(f"Error processing webhook: {e}") return { 'statusCode': 500, 'body': json.dumps({'error': 'Internal server error'}) } def verify_signature(event, webhook_secret): """ Verify the webhook signature using HMAC """ try: # Get the signature from headers signature = event['headers'].get('x-webhook-signature') if not signature: print("Error: Missing webhook signature in headers") return False # Get the raw body (return an empty string if the body key doesn't exist) body = event.get('body', '') # Create HMAC using the secret key expected_signature = hmac.new( webhook_secret.encode('utf-8'), body.encode('utf-8'), hashlib.sha256 ).hexdigest() # Compare the expected signature with the received signature to authenticate the message is_valid = hmac.compare_digest(signature, expected_signature) if not is_valid: print(f"Error: Invalid signature. Received: {signature}, Expected: {expected_signature}") return False return True except Exception as e: print(f"Error verifying signature: {e}") return False
  4. DEPLOY 區段中,選擇部署以更新函數的程式碼。

Node.js
建立 Lambda 函式
  1. 開啟 Lambda 主控台中的函數頁面

  2. 執行下列動作,建立基本的 'Hello world' 函式:

    1. 選擇 Create function (建立函數)

    2. 選取從頭開始撰寫

    3. 針對函數名稱,請輸入 myLambdaWebhook

    4. 針對執行期,選取 nodejs24.x。

    5. 選擇 Create function (建立函數)

  3. 程式碼來源窗格中,複製並貼上以下項目來取代現有程式碼:

    import crypto from 'crypto'; export const handler = async (event, context) => { // Get the webhook secret from environment variables const webhookSecret = process.env.WEBHOOK_SECRET; // Verify the webhook signature if (!verifySignature(event, webhookSecret)) { return { statusCode: 401, body: JSON.stringify({ error: 'Invalid signature' }) }; } try { // Parse the webhook payload const payload = JSON.parse(event.body); // Handle different event types const eventType = payload.type; switch (eventType) { case 'payment.success': { // Handle successful payment const orderId = payload.orderId; console.log(`Processing successful payment for order ${orderId}`); // Add your business logic here // For example, update database, send notifications, etc. break; } case 'payment.failed': { // Handle failed payment const orderId = payload.orderId; console.log(`Processing failed payment for order ${orderId}`); // Add your business logic here break; } default: console.log(`Received unhandled event type: ${eventType}`); } // Return success response return { statusCode: 200, body: JSON.stringify({ received: true }) }; } catch (error) { if (error instanceof SyntaxError) { // Handle JSON parsing errors return { statusCode: 400, body: JSON.stringify({ error: 'Invalid JSON payload' }) }; } // Handle all other errors console.error('Error processing webhook:', error); return { statusCode: 500, body: JSON.stringify({ error: 'Internal server error' }) }; } }; // Verify the webhook signature using HMAC const verifySignature = (event, webhookSecret) => { try { // Get the signature from headers const signature = event.headers['x-webhook-signature']; if (!signature) { console.log('No signature found in headers:', event.headers); return false; } // Get the raw body (return an empty string if the body key doesn't exist) const body = event.body || ''; // Create HMAC using the secret key const hmac = crypto.createHmac('sha256', webhookSecret); const expectedSignature = hmac.update(body).digest('hex'); // Compare expected and received signatures const isValid = signature === expectedSignature; if (!isValid) { console.log(`Invalid signature. Received: ${signature}, Expected: ${expectedSignature}`); return false; } return true; } catch (error) { console.error('Error during signature verification:', error); return false; } };
  4. DEPLOY 區段中,選擇部署以更新函數的程式碼。

建立秘密金鑰

Lambda 函式若要驗證 Webhook 請求,會使用一個與呼叫應用程式共用的秘密金鑰。在此範例中,金鑰存放在環境變數中。但在生產應用程式中,請勿在函數程式碼中包含密碼等敏感資訊。反之,請建立 AWS Secrets Manager 秘密然後使用 AWS 參數和秘密 Lambda 延伸來擷取 Lambda 函數中的登入資料。

建立並儲存 Webhook 秘密金鑰
  1. 使用密碼編譯安全的隨機數字產生器,產生一個長的隨機字串。您可以使用下列以 Python 或 Node.js 編寫的程式碼片段來生成並列印 32 字元的秘密,也可以使用自己慣用的方法。

    Python
    範例 產生秘密的程式碼
    import secrets webhook_secret = secrets.token_urlsafe(32) print(webhook_secret)
    Node.js
    範例 產生秘密的程式碼 (ES 模組格式)
    import crypto from 'crypto'; let webhookSecret = crypto.randomBytes(32).toString('base64'); console.log(webhookSecret)
  2. 執行下列動作,將產生的字串儲存為函式的環境變數:

    1. 在函式的組態索引標籤中,選取環境變數

    2. 選擇編輯

    3. 選擇 Add environment variable (新增環境變數)。

    4. 金鑰欄位中,輸入 WEBHOOK_SECRET;接著在欄位中,輸入上一個步驟中產生的秘密。

    5. 選擇儲存

本教學課程後續將再次用到此金鑰來測試函式,因此請立即記錄下來。

建立函式 URL 端點

使用 Lambda 函式 URL 為 Webhook 建立端點。由於您使用身分驗證類型 NONE 來建立具有公有存取權的端點,因此任何擁有 URL 的使用者都可以調用函式。若要進一步了解如何控制函式 URL 的存取權,請參閱控制對 Lambda 函數 URL 的存取。如果 Webhook 需要更進階的身分驗證選項,建議採用 API Gateway。

建立函式 URL 端點
  1. 在函式的組態索引標籤中,選取函式 URL

  2. 選擇 Create function URL (建立函數 URL)。

  3. 身分驗證類型欄位中,選取 NONE

  4. 選擇儲存

剛剛建立的函式 URL 端點會顯示在函式 URL 窗格中。複製端點,在教學課程後續部分使用。

在主控台中測試函式

在透過 URL 端點使用 HTTP 請求調用函式之前,請先在主控台中測試,確認程式碼如預期運作。

若要在主控台中驗證函式,需先使用之前在教學課程中產生的秘密,搭配以下測試用 JSON 承載資料計算 Webhook 簽章:

{ "type": "payment.success", "orderId": "1234", "amount": "99.99" }

利用下列其中一個以 Python 或 Node.js 編寫的程式碼範例,並使用您自己的秘密來計算 Webhook 簽章。

Python
計算 Webhook 簽章
  1. 將下列程式碼儲存為名為 calculate_signature.py 的檔案。將程式碼中的 Webhook 秘密取代為您自己的值。

    import secrets import hmac import json import hashlib webhook_secret = "arlbSDCP86n_1H90s0fL_Qb2NAHBIBQOyGI0X4Zay4M" body = json.dumps({"type": "payment.success", "orderId": "1234", "amount": "99.99"}) signature = hmac.new( webhook_secret.encode('utf-8'), body.encode('utf-8'), hashlib.sha256 ).hexdigest() print(signature)
  2. 從儲存程式碼的同一目錄中執行下列命令來計算簽章。複製程式碼輸出的簽章。

    python calculate_signature.py
Node.js
計算 Webhook 簽章
  1. 將下列程式碼儲存為名為 calculate_signature.mjs 的檔案。將程式碼中的 Webhook 秘密取代為您自己的值。

    import crypto from 'crypto'; const webhookSecret = "arlbSDCP86n_1H90s0fL_Qb2NAHBIBQOyGI0X4Zay4M" const body = "{\"type\": \"payment.success\", \"orderId\": \"1234\", \"amount\": \"99.99\"}"; let hmac = crypto.createHmac('sha256', webhookSecret); let signature = hmac.update(body).digest('hex'); console.log(signature);
  2. 從儲存程式碼的同一目錄中執行下列命令來計算簽章。複製程式碼輸出的簽章。

    node calculate_signature.mjs

現在,您可以在主控台中使用測試 HTTP 請求來測試函式程式碼。

在主控台中測試函式
  1. 選取函式的程式碼索引標籤。

  2. 測試事件區段中,選擇建立新測試事件

  3. Event Name (事件名稱) 輸入 myEvent

  4. 將下列項目複製並貼入事件 JSON 窗格中,取代現有的 JSON。將 Webhook 簽章取代為在上一個步驟中計算得出的值。

    { "headers": { "Content-Type": "application/json", "x-webhook-signature": "2d672e7a0423fab740fbc040e801d1241f2df32d2ffd8989617a599486553e2a" }, "body": "{\"type\": \"payment.success\", \"orderId\": \"1234\", \"amount\": \"99.99\"}" }
  5. 選擇儲存

  6. 選擇調用

    您應該會看到類似下列的輸出:

    Python
    Status: Succeeded Test Event Name: myEvent Response: { "statusCode": 200, "body": "{\"received\": true}" } Function Logs: START RequestId: 50cc0788-d70e-453a-9a22-ceaa210e8ac6 Version: $LATEST Processing successful payment for order 1234 END RequestId: 50cc0788-d70e-453a-9a22-ceaa210e8ac6 REPORT RequestId: 50cc0788-d70e-453a-9a22-ceaa210e8ac6 Duration: 1.55 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 36 MB Init Duration: 136.32 ms
    Node.js
    Status: Succeeded Test Event Name: myEvent Response: { "statusCode": 200, "body": "{\"received\":true}" } Function Logs: START RequestId: e54fe6c7-1df9-4f05-a4c4-0f71cacd64f4 Version: $LATEST 2025-01-10T18:05:42.062Z e54fe6c7-1df9-4f05-a4c4-0f71cacd64f4 INFO Processing successful payment for order 1234 END RequestId: e54fe6c7-1df9-4f05-a4c4-0f71cacd64f4 REPORT RequestId: e54fe6c7-1df9-4f05-a4c4-0f71cacd64f4 Duration: 60.10 ms Billed Duration: 61 ms Memory Size: 128 MB Max Memory Used: 72 MB Init Duration: 174.46 ms Request ID: e54fe6c7-1df9-4f05-a4c4-0f71cacd64f4

使用 HTTP 請求測試函式

使用 curl 命令列工具來測試 Webhook 端點。

使用 HTTP 請求測試函式
  1. 在終端或 Shell 程式中,執行下列 curl 命令。將 URL 取代為您自己的函式 URL 端點的值,並將 Webhook 簽章取代為您使用自己的秘密金鑰計算得出的簽章。

    curl -X POST https://ryqgmbx5xjzxahif6frvzikpre0bpvpf.lambda-url.us-west-2.on.aws/ \ -H "Content-Type: application/json" \ -H "x-webhook-signature: d5f52b76ffba65ff60ea73da67bdf1fc5825d4db56b5d3ffa0b64b7cb85ef48b" \ -d '{"type": "payment.success", "orderId": "1234", "amount": "99.99"}'

    您應該會看到下列輸出:

    {"received": true}
  2. 執行下列動作,檢查函式的 CloudWatch 日誌,確認其正確剖析了承載:

    1. 在 Amazon CloudWatch 主控台中,開啟日誌群組頁面。

    2. 選取函式的日誌群組 (/aws/lambda/myLambdaWebhook)。

    3. 選取最新的日誌串流。

      您應該會在函式日誌中看到類似如下的輸出:

      Python
      Processing successful payment for order 1234
      Node.js
      2025-01-10T18:05:42.062Z e54fe6c7-1df9-4f05-a4c4-0f71cacd64f4 INFO Processing successful payment for order 1234
  3. 執行下列 curl 命令,確認程式碼偵測到無效的簽章。將 URL 取代為您自己的函式 URL 端點。

    curl -X POST https://ryqgmbx5xjzxahif6frvzikpre0bpvpf.lambda-url.us-west-2.on.aws/ \ -H "Content-Type: application/json" \ -H "x-webhook-signature: abcdefg" \ -d '{"type": "payment.success", "orderId": "1234", "amount": "99.99"}'

    您應該會看到下列輸出:

    {"error": "Invalid signature"}

清除您的資源

除非您想要保留為此教學課程建立的資源,否則您現在便可刪除。透過刪除您不再使用 AWS 的資源,您可以避免不必要的 費用 AWS 帳戶。

若要刪除 Lambda 函數
  1. 開啟 Lambda 主控台中的 函數頁面

  2. 選擇您建立的函數。

  3. 選擇 Actions (動作)、Delete (刪除)。

  4. 在文字輸入欄位中輸入 confirm,然後選擇刪除

當您在主控台中建立 Lambda 函式時,Lambda 也會為函式建立執行角色

刪除執行角色
  1. 開啟 IAM 主控台中的 角色頁面

  2. 選取 Lambda 建立的執行角色。角色名稱具有格式 myLambdaWebhook-role-<random string>

  3. 選擇 刪除

  4. 在文字輸入欄位中輸入角色的名稱,然後選擇刪除