メインコンテンツまでスキップ
Asgard の Automation Tool で Webhook のエンドポイントを作る完全な例

Webhook 連携

Asgard の Automation Tool を使うと、Workflow の中に Webhook のエンドポイントを定義し、外部システムからのイベントを受け取って自動化の処理を実行できます。よくある使いどころは次のとおりです。

  • GitHub / GitLab の push イベントを受けて Slack に自動で通知する
  • EC の注文イベントを処理して顧客に返信する
  • 外部の CRM / ERP のデータ更新を取り込む

Automation Tool の Workflow の考え方​

典型的な Webhook の Workflow は 3 つの段階でできています。

外部システム → Webhook のエンドポイント
↓
1. Validate Payload(リクエストの形式と署名を検証)
↓
2. Process(データベースの検索や LLM の呼び出しなど、業務のロジック)
↓
3. Response(呼び出し元へ結果を返す)

Asgard で Automation Tool を設定する​

手順 1: Workflow を作る​

  1. Workflow のページで「新しい Workflow を作成」を押します
  2. Workflow に Automation Tool のノードを追加します
  3. トリガーの条件を「Webhook」にします

手順 2: Payload の検証を設定する​

Automation Tool の設定では次の内容を定義できます。

  • Payload Schema — 届く JSON の構造を検証します
  • Secret Signature — Webhook の送信元を検証します(有効にすることを推奨します)
  • 許可する Content-Type — application/json など

手順 3: App を公開し Webhook URL を取得する​

App を公開すると、Asgard が次の形式で Webhook のエンドポイントを発行します。

POST https://api.asgard-ai.com/generic/ns/{{namespace}}/bot-provider/{{bot_provider_name}}/message/sse

Webhook Payload の例​

EC の注文イベントの典型的な Payload です。

{
"customChannelId": "order-webhook-channel",
"customMessageId": "order-event-12345",
"text": "新規注文の通知",
"action": "NONE",
"payload": {
"event": "order.created",
"orderId": "ORD-2024-12345",
"customer": {
"name": "山田 太郎",
"email": "wang@example.com"
},
"items": [
{
"productId": "PROD-001",
"name": "ワイヤレスイヤホン",
"quantity": 1,
"price": 1290
}
],
"total": 1290,
"currency": "TWD",
"createdAt": "2024-01-15T10:30:00Z"
}
}

Webhook エンドポイントを呼び出す​

cURL の例​

curl -X POST "https://api.asgard-ai.com/generic/ns/your-namespace/bot-provider/your-bot-provider/message/sse" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-Webhook-Signature: sha256=your-hmac-signature" \
-d '{
"customChannelId": "order-webhook-channel",
"customMessageId": "order-event-12345",
"text": "新規注文の通知",
"action": "NONE"
}'

JavaScript(Node.js)の Webhook サーバーの例​

Node.js の Express アプリケーションで外部の Webhook を受け取り、Asgard に転送する例です。

const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch');

const app = express();
app.use(express.json());

const ASGARD_API_KEY = process.env.ASGARD_API_KEY;
const ASGARD_BASE_URL = 'https://api.asgard-ai.com';
const NAMESPACE = 'your-namespace';
const BOT_PROVIDER = 'your-bot-provider';
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

/**
* Webhook の署名を検証する(HMAC-SHA256)
*/
function verifySignature(payload, signature, secret) {
const expected = `sha256=${crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex')}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

/**
* GitHub の Push イベントを受け取り、Asgard の Workflow を起動する
*/
app.post('/webhook/github', async (req, res) => {
const signature = req.headers['x-hub-signature-256'];

// 署名を検証する
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: '署名の検証に失敗しました' });
}

const { repository, pusher, commits } = req.body;
const channelId = `github-${repository.name}`;
const messageId = `push-${Date.now()}`;

// Asgard に送るメッセージを組み立てる
const message = `GitHub の Push: ${pusher.name} が ${repository.full_name} に ${commits.length} 件の commit を push しました`;

try {
const asgardUrl = `${ASGARD_BASE_URL}/generic/ns/${NAMESPACE}/bot-provider/${BOT_PROVIDER}/message/sse`;

const asgardResponse = await fetch(asgardUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': ASGARD_API_KEY,
},
body: JSON.stringify({
customChannelId: channelId,
customMessageId: messageId,
text: message,
action: 'NONE',
}),
});

if (!asgardResponse.ok) {
throw new Error(`Asgard API error: ${asgardResponse.status}`);
}

// Asgard のストリーミング応答を読む
const reader = asgardResponse.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let result = '';

while (true) {
const { done, value } = await reader.read();
if (done) break;

buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';

for (const line of lines) {
if (!line.startsWith('data:')) continue;
const jsonStr = line.slice(5).trim();
if (!jsonStr) continue;

try {
const event = JSON.parse(jsonStr);
if (event.eventType === 'asgard.message.complete') {
result = event.fact.messageComplete.message.text;
}
if (event.eventType === 'asgard.run.done') {
return res.json({ success: true, result });
}
} catch (_) {}
}
}

res.json({ success: true, result });
} catch (error) {
console.error('Asgard への転送に失敗しました:', error);
res.status(500).json({ error: error.message });
}
});

app.listen(3000, () => {
console.log('Webhook サーバーは http://localhost:3000 で動いています');
});

Python(FastAPI)の Webhook サーバーの例​

import hashlib
import hmac
import json
import os
import time
from fastapi import FastAPI, Request, HTTPException, Header
from typing import Optional
import requests

app = FastAPI()

ASGARD_API_KEY = os.environ.get("ASGARD_API_KEY")
ASGARD_BASE_URL = "https://api.asgard-ai.com"
NAMESPACE = "your-namespace"
BOT_PROVIDER = "your-bot-provider"
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "").encode()


def verify_signature(payload: bytes, signature: str) -> bool:
"""HMAC-SHA256 の署名を検証する"""
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET,
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)


def call_asgard(channel_id: str, message: str) -> str:
"""Asgard API を呼び出し、応答を受け取る"""
url = f"{ASGARD_BASE_URL}/generic/ns/{NAMESPACE}/bot-provider/{BOT_PROVIDER}/message/sse"

headers = {
"Content-Type": "application/json",
"X-API-KEY": ASGARD_API_KEY,
}

payload = {
"customChannelId": channel_id,
"customMessageId": f"webhook-{int(time.time())}",
"text": message,
"action": "NONE",
}

result = ""

with requests.post(url, headers=headers, json=payload, stream=True) as response:
response.raise_for_status()

for line in response.iter_lines():
if not line:
continue

decoded = line.decode("utf-8")
if not decoded.startswith("data:"):
continue

json_str = decoded[5:].strip()
if not json_str:
continue

try:
event = json.loads(json_str)
if event.get("eventType") == "asgard.message.complete":
result = event["fact"]["messageComplete"]["message"]["text"]
elif event.get("eventType") == "asgard.run.done":
break
except json.JSONDecodeError:
pass

return result


@app.post("/webhook/order")
async def handle_order_webhook(
request: Request,
x_webhook_signature: Optional[str] = Header(None),
):
body = await request.body()

# 署名を検証する
if x_webhook_signature and WEBHOOK_SECRET:
if not verify_signature(body, x_webhook_signature):
raise HTTPException(status_code=401, detail="署名の検証に失敗しました")

data = json.loads(body)
order_id = data.get("orderId", "unknown")
customer_name = data.get("customer", {}).get("name", "unknown")
event_type = data.get("event", "unknown")

# Asgard に送るメッセージを組み立てる
message = f"注文イベント: {event_type}、注文番号: {order_id}、顧客: {customer_name}"
channel_id = f"order-{order_id}"

result = call_asgard(channel_id, message)

return {"success": True, "orderId": order_id, "result": result}

安全に運用するために​

注意

外部に公開する Webhook のエンドポイントを作るときは、次の点にご注意ください。

  1. 署名の検証を有効にする — HMAC-SHA256 で送信元を検証し、偽装されたリクエストを防ぎます
  2. IP の許可リストを設定する — 想定している IP の範囲からのリクエストだけを受け付けます
  3. 応答に機密情報を含めない — Webhook の応答には必要な状態だけを入れます
  4. リクエストのタイムアウトを設定する — 長く待ち続けてリソースを占有しないようにします
  5. すべてのリクエストを記録する — あとからの監査と調査がしやすくなります

次に読む​