Skip to content

Custom Tools

This guide explains how to create custom webhook-based tools that your agents can call.

What are Custom Tools?

Custom tools let you extend your agents' capabilities by defining webhook endpoints they can call. Unlike MCP servers (which require running a server), custom tools work with any HTTP endpoint you control.

Use cases: - Connect agents to internal APIs - Integrate with CRMs, ticketing systems, ERPs - Trigger automated workflows - Access private databases or services

Architecture

┌─────────────────────────────────────────────────────────────┐
│                         Ag2Trust                            │
│                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐    │
│  │   Agent     │    │   Agent     │    │   Agent     │    │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘    │
│         │                  │                  │            │
│         └──────────────────┼──────────────────┘            │
│                           │                                │
│                    ┌──────▼──────┐                        │
│                    │ MCP Gateway │                        │
│                    │ (proxy)     │                        │
│                    └──────┬──────┘                        │
└───────────────────────────┼─────────────────────────────────┘
              ┌─────────────┼─────────────┐
              ▼             ▼             ▼
        ┌─────────┐   ┌─────────┐   ┌─────────┐
        │ Your    │   │ Your    │   │  Your   │
        │ Webhook │   │ Webhook │   │ Webhook │
        │ (CRM)   │   │ (ERP)   │   │ (Custom)│
        └─────────┘   └─────────┘   └─────────┘

Step 1: Create a Custom Tool

Via Dashboard

  1. Go to Tools in the sidebar
  2. Click Add Tool
  3. Fill in the tool details:
Field Description Example
Tool Slug Unique identifier (lowercase, underscores) send_invoice
Display Name Human-readable name "Send Invoice"
Description When agents should use this tool "Sends an invoice to a customer via email. Use when customer requests billing."
Webhook URL Your HTTPS endpoint https://api.example.com/webhooks/invoice
Authentication HMAC-SHA256 (recommended) or Bearer Token HMAC
Parameter Schema JSON Schema for parameters See below

Parameter Schema Example

{
  "type": "object",
  "properties": {
    "customer_id": {
      "type": "string",
      "description": "The customer ID"
    },
    "amount": {
      "type": "number",
      "description": "Invoice amount in cents"
    },
    "description": {
      "type": "string",
      "description": "Line item description"
    }
  },
  "required": ["customer_id", "amount"]
}

Save Your Secret

After creating an HMAC tool, the webhook secret is shown once. Save it immediately - you'll need it to verify signatures.

Step 2: Assign to Agent Types

Custom tools must be assigned to agent types before agents can use them:

  1. Go to Agents > Agent Types
  2. Edit the agent type
  3. Under Custom Tools, select the tools to enable
  4. Save changes

Step 3: Implement Your Webhook

Request Format

Ag2Trust sends POST requests to your webhook URL:

POST /webhooks/invoice HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Request-ID: req_abc123
X-Tool-Slug: send_invoice

{
  "customer_id": "cust_123",
  "amount": 5000,
  "description": "Monthly subscription"
}

For HMAC authentication, verify the signature before processing:

import hmac
import hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

# In your webhook handler
@app.post("/webhooks/invoice")
async def handle_invoice(request: Request):
    body = await request.body()
    signature = request.headers.get("X-Webhook-Signature", "")

    if not verify_signature(body, signature, WEBHOOK_SECRET):
        raise HTTPException(401, "Invalid signature")

    data = json.loads(body)
    # Process the request...
    return {"success": True, "invoice_id": "inv_123"}
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature)
  );
}

// In your Express handler
app.post('/webhooks/invoice', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-webhook-signature'] || '';

  if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const data = JSON.parse(req.body);
  // Process the request...
  res.json({ success: true, invoice_id: 'inv_123' });
});

Response Format

Return a JSON response with the result:

{
  "success": true,
  "invoice_id": "inv_123",
  "message": "Invoice sent to customer@example.com"
}

The response is passed back to the agent as the tool result.

Error Handling

Return appropriate HTTP status codes:

Status Meaning Agent Behavior
200-299 Success Tool result returned to agent
400 Bad request Error shown to agent
401/403 Auth failure Error logged, tool may be disabled
500+ Server error Counts toward circuit breaker

Circuit Breaker

Custom tools have a built-in circuit breaker for reliability:

  • 3 consecutive failures automatically disable the tool
  • Status changes to "Circuit Breaker" in the dashboard
  • Tool must be manually re-enabled after fixing the issue

To re-enable: 1. Fix the underlying issue with your webhook 2. Go to Tools in the dashboard 3. Click Enable on the affected tool

Security Features

SSRF Protection

Ag2Trust blocks webhooks to: - Private IP ranges (10.x, 172.16-31.x, 192.168.x) - Localhost (127.0.0.1, ::1) - Cloud metadata endpoints (169.254.169.254) - Link-local addresses

Secret Rotation

Rotate your HMAC secret periodically:

  1. Go to Tools in the dashboard
  2. Click Rotate Secret on the tool
  3. Update your webhook with the new secret
  4. The old secret stops working immediately

Update Webhook First

Consider updating your webhook to accept both old and new secrets during rotation to avoid downtime.

Credential Encryption

All secrets (HMAC keys, Bearer tokens) are: - Encrypted with AWS KMS (AES-256-GCM) - Never stored in plain text - Decrypted only when proxying requests

Example: CRM Integration

1. Create the Tool

Slug: lookup_customer
Display Name: Look Up Customer
Description: Retrieves customer information from the CRM. Use when you need customer details like contact info, purchase history, or account status.
Webhook URL: https://crm.example.com/api/webhook/lookup
Auth: HMAC-SHA256

Schema:

{
  "type": "object",
  "properties": {
    "customer_id": {
      "type": "string",
      "description": "Customer ID or email"
    },
    "fields": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Fields to retrieve (name, email, phone, purchases)"
    }
  },
  "required": ["customer_id"]
}

2. Assign to Support Agent Type

Enable lookup_customer for the "Support Agent" agent type.

3. Implement Webhook

@app.post("/api/webhook/lookup")
async def lookup_customer(request: Request):
    # Verify signature (see above)

    data = await request.json()
    customer = db.get_customer(data["customer_id"])

    if not customer:
        return {"found": False, "message": "Customer not found"}

    fields = data.get("fields", ["name", "email"])
    result = {k: getattr(customer, k) for k in fields if hasattr(customer, k)}
    result["found"] = True

    return result

4. Test with Agent

User: What's the status of customer C-12345?

Agent: Let me look up that customer for you.

[Agent calls: custom_lookup_customer(customer_id="C-12345")]

Agent: Customer C-12345 is John Smith (john@example.com).
Their account is active with 3 previous purchases totaling $450.

Tier Availability

Tier Custom Tools
Free Not available
Starter Unlimited
Pro Unlimited
Enterprise Unlimited

Troubleshooting

"Tool disabled by circuit breaker"

Your webhook returned 3+ consecutive errors. Check: 1. Webhook URL is correct and reachable 2. Authentication is configured properly 3. Your server is returning valid JSON 4. No firewall blocking Ag2Trust IPs

"Invalid signature"

HMAC verification failing: 1. Verify you're using the correct secret 2. Ensure you're hashing the raw request body 3. Check the signature format: sha256={hex_digest}

"Connection refused"

  1. Webhook must be publicly accessible (not localhost)
  2. URL must use HTTPS
  3. Check firewall rules allow inbound connections

Best Practices

1. Use HMAC Authentication

HMAC-SHA256 signatures verify requests came from Ag2Trust: - Harder to forge than Bearer tokens - Secrets never sent over the wire - Each request has unique signature

2. Validate Input

Even with signature verification, validate parameters:

if not data.get("customer_id"):
    return {"error": "customer_id required"}, 400

3. Return Helpful Responses

Agents work better with descriptive responses:

{
  "success": true,
  "invoice_id": "inv_123",
  "message": "Invoice for $50.00 sent to john@example.com",
  "due_date": "2025-02-15"
}

4. Log Request IDs

Include X-Request-ID in logs for debugging:

logger.info(f"Processing request {request.headers.get('X-Request-ID')}")

5. Handle Timeouts

Ag2Trust has a 30-second timeout. For long operations: - Return immediately with a job ID - Let the agent poll for status

Next Steps