# Webhook Tools

> Call external HTTP endpoints as agent tools.

**URL:** https://inference.sh/docs/api/agent/webhook-tools
**Last updated:** 2026-10-11

---

Call external HTTP endpoints as agent tools.

> **Authenticated REST APIs:** For bearer tokens, API keys, or OAuth credentials injected by the runtime, use [Call Tools](/docs/api/agent/call-tools) instead. In the JavaScript SDK, define these with `httpTool()` or `callTool()` (aliases). Python uses `call_tool()`. Webhook tools are for endpoints that accept a POST body from inference shell (optionally HMAC-signed with `.secret()`).

---

## Basic Usage

<div data-type="codegroup">

```python Python
from inferencesh import webhook_tool, string

inventory = (
    webhook_tool("check_inventory", "https://api.example.com/inventory")
    .describe("Check product inventory levels")
    .param("sku", string("Product SKU"))
    .build()
)
```

```typescript JavaScript
import { webhookTool, string } from '@inferencesh/sdk';

const inventory = webhookTool('check_inventory', 'https://api.example.com/inventory')
  .describe('Check product inventory levels')
  .param('sku', string('Product SKU'))
  .build();
```

</div>

---

## How It Works

When the agent calls a webhook tool:
1. inference shell sends a POST request to your URL
2. Request body contains tool arguments as JSON
3. Your server processes and returns JSON response
4. Response becomes the tool result

---

## Builder Methods

| Method | Description |
|--------|-------------|
| `webhookTool(name, url)` | Create webhook tool |
| `.describe(text)` | Set description for LLM |
| `.display(name)` | Human-readable name |
| `.param(name, schema)` | Define expected parameters |
| `.secret(key)` | Signing secret for verification |
| `.requireApproval()` | Require user approval |
| `.build()` | Build the tool |

---

## Request Format

Your endpoint receives:

```json
POST /your-endpoint
Content-Type: application/json
X-Webhook-Signature: sha256=...

{
  "sku": "WIDGET-123"
}
```

---

## Response Format

Return JSON:

```json
{
  "in_stock": true,
  "quantity": 42,
  "warehouse": "NYC"
}
```

---

## Webhook Signing

Verify requests are from inference shell:

<div data-type="codegroup">

```python Python
webhook = (
    webhook_tool("secure_action", "https://api.example.com/action")
    .describe("Perform a secure action")
    .secret("your_webhook_secret")  # Secret for HMAC signing
    .build()
)
```

```typescript JavaScript
const webhook = webhookTool('secure_action', 'https://api.example.com/action')
  .describe('Perform a secure action')
  .secret('your_webhook_secret')
  .build();
```

</div>

When an agent version is saved, each hook secret and each literal HTTP header value is stored as an internal encrypted secret of the version's team, and the tool keeps a reference to it. It is not a row in your Secrets list, and reading the agent does not return the value.

---

## Verifying Signatures

<div data-type="codegroup">

```python Python
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)
```

```typescript JavaScript
import { createHmac, timingSafeEqual } from 'crypto';

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

</div>

---

## With Parameters

<div data-type="codegroup">

```python Python
from inferencesh import webhook_tool, string, number, obj

order_api = (
    webhook_tool("create_order", "https://api.example.com/orders")
    .describe("Create a new order in the system")
    .param("product", string("Product name"))
    .param("quantity", number("Order quantity"))
    .param("customer", obj({
        "name": string("Customer name"),
        "email": string("Customer email")
    }, "Customer details"))
    .secret("order_webhook_secret")
    .require_approval()  # Orders need approval
    .build()
)
```

```typescript JavaScript
import { webhookTool, string, number, obj } from '@inferencesh/sdk';

const orderApi = webhookTool('create_order', 'https://api.example.com/orders')
  .describe('Create a new order in the system')
  .param('product', string('Product name'))
  .param('quantity', number('Order quantity'))
  .param('customer', obj({
    name: string('Customer name'),
    email: string('Customer email')
  }, 'Customer details'))
  .secret('order_webhook_secret')
  .requireApproval()
  .build();
```

</div>

---

## Example Server

<div data-type="codegroup">

```python Python (FastAPI)
from fastapi import FastAPI, Request, Header
import hmac
import hashlib

app = FastAPI()

@app.post("/inventory")
async def check_inventory(
    request: Request,
    x_webhook_signature: str = Header(None)
):
    body = await request.body()
    
    # Verify signature
    if not verify_signature(body, x_webhook_signature, "your_secret"):
        return {"error": "Invalid signature"}, 401
    
    data = await request.json()
    sku = data["sku"]
    
    # Your logic here
    return {
        "in_stock": True,
        "quantity": 42
    }
```

```typescript JavaScript (Express)
import express from 'express';
import { createHmac, timingSafeEqual } from 'crypto';

const app = express();
app.use(express.raw({ type: 'application/json' }));

app.post('/inventory', (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  
  // Verify signature
  if (!verifySignature(req.body, signature, 'your_secret')) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  const data = JSON.parse(req.body.toString());
  const { sku } = data;
  
  // Your logic here
  res.json({
    in_stock: true,
    quantity: 42
  });
});
```

</div>
