Skip to main content

REFERENCE

Developer Reference

Authentication, APIs, SDKs, Flow Agent processors and parameter resolution, in full.

Webhook integration

A complete example of building a webhook endpoint with an Asgard Automation Tool

Asgard's Automation Tool lets you define a webhook endpoint inside a workflow, receive events from an outside system, and run whatever automation you need. It comes up in cases like:

  • Notifying Slack automatically on a GitHub or GitLab push
  • Handling an e-commerce order event and replying to the customer
  • Picking up data changes from a third-party CRM or ERP
  1. What an Automation Tool workflow looks like​

    A webhook workflow usually has three stages:

    the outside system → the webhook endpoint
    ↓
    1. Validate Payload (checks the request format and the signature)
    ↓
    2. Process (the business logic: query a database, call an LLM)
    ↓
    3. Response (returns the result to the caller)
  2. Setting up an Automation Tool in Asgard​

    Step 1: create the workflow​

    1. Go to Workflow and click to create a new one
    2. Add an Automation Tool node to it
    3. Set the trigger to "Webhook"

    Step 2: configure payload validation​

    In the Automation Tool's settings you can define:

    • Payload Schema — validates the JSON that arrives
    • Secret Signature — verifies where the webhook came from; turn this on
    • Allowed Content-Type — for example application/json

    Step 3: publish the app and get the webhook URL​

    Once the app is published, Asgard gives you a webhook endpoint:

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

    A typical e-commerce order event:

    {
    "customChannelId": "order-webhook-channel",
    "customMessageId": "order-event-12345",
    "text": "New order",
    "action": "NONE",
    "payload": {
    "event": "order.created",
    "orderId": "ORD-2024-12345",
    "customer": {
    "name": "Alex Chen",
    "email": "wang@example.com"
    },
    "items": [
    {
    "productId": "PROD-001",
    "name": "Wireless earbuds",
    "quantity": 1,
    "price": 1290
    }
    ],
    "total": 1290,
    "currency": "TWD",
    "createdAt": "2024-01-15T10:30:00Z"
    }
    }
  4. Calling the webhook endpoint​

    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": "New order",
    "action": "NONE"
    }'

    A Node.js webhook server​

    Receiving an outside webhook in a Node.js Express application and forwarding it to 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;

    /**
    * Verify the webhook signature (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)
    );
    }

    /**
    * Receive a GitHub push event and trigger an Asgard workflow
    */
    app.post('/webhook/github', async (req, res) => {
    const signature = req.headers['x-hub-signature-256'];

    // verify the signature
    if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Signature verification failed' });
    }

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

    // build the message to send to Asgard
    const message = `GitHub push: ${pusher.name} pushed ${commits.length} commit(s) to ${repository.full_name}`;

    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}`);
    }

    // read Asgard's streamed response
    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('Could not forward to Asgard:', error);
    res.status(500).json({ error: error.message });
    }
    });

    app.listen(3000, () => {
    console.log('Webhook server listening on http://localhost:3000');
    });

    A Python (FastAPI) webhook server​

    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:
    """Verify the HMAC-SHA256 signature"""
    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:
    """Call the Asgard API and read the response"""
    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()

    # verify the signature
    if x_webhook_signature and WEBHOOK_SECRET:
    if not verify_signature(body, x_webhook_signature):
    raise HTTPException(status_code=401, detail="Signature verification failed")

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

    # build the message to send to Asgard
    message = f"Order event: {event_type}, order {order_id}, customer {customer_name}"
    channel_id = f"order-{order_id}"

    result = call_asgard(channel_id, message)

    return {"success": True, "orderId": order_id, "result": result}
  5. Keeping it secure​

    caution

    When you expose a webhook endpoint, pay attention to the following.

    1. Turn on signature verification — use HMAC-SHA256 to check where the webhook came from, so a request cannot be forged
    2. Restrict by IP — accept requests only from the IP ranges you expect
    3. Do not leak anything sensitive in the response — a webhook response should carry only the status
    4. Set a request timeout — so a long wait does not tie up resources
    5. Log every request — which is what makes auditing and debugging possible afterwards
  6. Next​

Ready to get started?

Want to see how Asgard fits your team? Talk to us.

Contact us