本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
教學課程:使用 Lambda 函式 URL 建立 Webhook 端點
在本教學課程中,您將建立 Lambda 函式 URL 來實作 Webhook 端點。Webhook 是一種輕量型的事件驅動型通訊機制,能透過 HTTP 自動在應用程式間傳輸資料。您可以使用 Webhook 來接收有關其他系統中發生事件的即時更新,例如當新客戶在網站上註冊、處理付款或上傳檔案時。
使用 Lambda 時,可以透過 Lambda 函式 URL 或 API Gateway 來實作 Webhook。對於不需要進階授權或請求驗證等功能的簡單 Webhook,函式 URL 是很好的選擇。
先決條件
要完成本教學課程,必須在本機電腦上安裝 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 函式
開啟 Lambda 主控台中的函數頁面。
-
執行下列動作,建立基本的 'Hello world' 函式:
-
選擇 Create function (建立函數)。
-
選取從頭開始撰寫。
-
針對函數名稱,請輸入 myLambdaWebhook。
-
針對執行期,選取 python3.14。
-
選擇 Create function (建立函數)。
-
在程式碼來源窗格中,複製並貼上以下項目來取代現有程式碼:
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
-
在 DEPLOY 區段中,選擇部署以更新函數的程式碼。
- Node.js
-
建立 Lambda 函式
開啟 Lambda 主控台中的函數頁面。
-
執行下列動作,建立基本的 'Hello world' 函式:
-
選擇 Create function (建立函數)。
-
選取從頭開始撰寫。
-
針對函數名稱,請輸入 myLambdaWebhook。
-
針對執行期,選取 nodejs24.x。
-
選擇 Create function (建立函數)。
-
在程式碼來源窗格中,複製並貼上以下項目來取代現有程式碼:
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;
}
};
-
在 DEPLOY 區段中,選擇部署以更新函數的程式碼。
建立秘密金鑰
Lambda 函式若要驗證 Webhook 請求,會使用一個與呼叫應用程式共用的秘密金鑰。在此範例中,金鑰存放在環境變數中。但在生產應用程式中,請勿在函數程式碼中包含密碼等敏感資訊。反之,請建立 AWS Secrets Manager 秘密,然後使用 AWS 參數和秘密 Lambda 延伸來擷取 Lambda 函數中的登入資料。
建立並儲存 Webhook 秘密金鑰
-
使用密碼編譯安全的隨機數字產生器,產生一個長的隨機字串。您可以使用下列以 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)
-
執行下列動作,將產生的字串儲存為函式的環境變數:
-
在函式的組態索引標籤中,選取環境變數。
-
選擇編輯。
-
選擇 Add environment variable (新增環境變數)。
-
在金鑰欄位中,輸入 WEBHOOK_SECRET;接著在值欄位中,輸入上一個步驟中產生的秘密。
-
選擇儲存。
本教學課程後續將再次用到此金鑰來測試函式,因此請立即記錄下來。
建立函式 URL 端點
使用 Lambda 函式 URL 為 Webhook 建立端點。由於您使用身分驗證類型 NONE 來建立具有公有存取權的端點,因此任何擁有 URL 的使用者都可以調用函式。若要進一步了解如何控制函式 URL 的存取權,請參閱控制對 Lambda 函數 URL 的存取。如果 Webhook 需要更進階的身分驗證選項,建議採用 API Gateway。
建立函式 URL 端點
-
在函式的組態索引標籤中,選取函式 URL。
-
選擇 Create function URL (建立函數 URL)。
-
在身分驗證類型欄位中,選取 NONE。
-
選擇儲存。
剛剛建立的函式 URL 端點會顯示在函式 URL 窗格中。複製端點,在教學課程後續部分使用。
在主控台中測試函式
在透過 URL 端點使用 HTTP 請求調用函式之前,請先在主控台中測試,確認程式碼如預期運作。
若要在主控台中驗證函式,需先使用之前在教學課程中產生的秘密,搭配以下測試用 JSON 承載資料計算 Webhook 簽章:
{
"type": "payment.success",
"orderId": "1234",
"amount": "99.99"
}
利用下列其中一個以 Python 或 Node.js 編寫的程式碼範例,並使用您自己的秘密來計算 Webhook 簽章。
- Python
-
計算 Webhook 簽章
-
將下列程式碼儲存為名為 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)
-
從儲存程式碼的同一目錄中執行下列命令來計算簽章。複製程式碼輸出的簽章。
python calculate_signature.py
- Node.js
-
計算 Webhook 簽章
-
將下列程式碼儲存為名為 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);
-
從儲存程式碼的同一目錄中執行下列命令來計算簽章。複製程式碼輸出的簽章。
node calculate_signature.mjs
現在,您可以在主控台中使用測試 HTTP 請求來測試函式程式碼。
在主控台中測試函式
-
選取函式的程式碼索引標籤。
-
在測試事件區段中,選擇建立新測試事件
-
Event Name (事件名稱) 輸入 myEvent。
-
將下列項目複製並貼入事件 JSON 窗格中,取代現有的 JSON。將 Webhook 簽章取代為在上一個步驟中計算得出的值。
{
"headers": {
"Content-Type": "application/json",
"x-webhook-signature": "2d672e7a0423fab740fbc040e801d1241f2df32d2ffd8989617a599486553e2a"
},
"body": "{\"type\": \"payment.success\", \"orderId\": \"1234\", \"amount\": \"99.99\"}"
}
-
選擇儲存。
-
選擇調用。
您應該會看到類似下列的輸出:
- 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 請求測試函式
-
在終端或 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}
-
執行下列動作,檢查函式的 CloudWatch 日誌,確認其正確剖析了承載:
-
在 Amazon CloudWatch 主控台中,開啟日誌群組頁面。
-
選取函式的日誌群組 (/aws/lambda/myLambdaWebhook)。
-
選取最新的日誌串流。
您應該會在函式日誌中看到類似如下的輸出:
- 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
-
執行下列 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 函數
-
開啟 Lambda 主控台中的 函數頁面。
-
選擇您建立的函數。
-
選擇 Actions (動作)、Delete (刪除)。
-
在文字輸入欄位中輸入 confirm,然後選擇刪除。
當您在主控台中建立 Lambda 函式時,Lambda 也會為函式建立執行角色。
刪除執行角色
-
開啟 IAM 主控台中的 角色頁面 。
-
選取 Lambda 建立的執行角色。角色名稱具有格式 myLambdaWebhook-role-<random string>。
-
選擇 刪除。
-
在文字輸入欄位中輸入角色的名稱,然後選擇刪除。