User-Supplied Tool Webhooks
Server-side execution of custom tools via webhooks during the managed tool-calling loop.
User-Supplied Tool Webhooks
Hypervize allows you to extend the model with your own custom tools by providing a webhook endpoint that we call on your behalf during the tool-calling loop. This gives you server-side execution of your custom logic (weather lookups, database queries, internal APIs, etc.) while still benefiting from Hypervize's managed tool orchestration.
We handle the full loop for you:
- The model decides to call your tool.
- We call your webhook with the arguments.
- We take the result and feed it back to the model as a tool message.
- The conversation continues (potentially with more tool calls) until the model produces a final answer.
This means your application code doesn't have to drive the tool-calling loop manually. You just expose a webhook that does one thing well.
How It Works
- In your chat completion request, include a
toolsarray with one or more tool definitions that have awebhookextension. - Hypervize automatically includes these tools (along with any Hypervize-managed tools you have enabled) when calling the underlying model.
- When the model outputs a tool call for one of your webhooks, our orchestration layer:
- Collects the complete tool call from the stream.
- POSTs a JSON payload to your registered
webhook.url. - Waits for your response.
- Treats the response as the tool result and continues the conversation with the model.
- The final response to you only contains the model's final answer (plus usage), unless you are using the raw tool-calling mode.
Because tool enablement is managed per-user (via the UI or attachments), the managed loop (including webhook calls) only activates for users who have tools enabled. If no tools are enabled for the request, we behave exactly like a standard OpenAI-compatible endpoint.
Defining a Webhook Tool
Add your tool to the tools array in the chat completion request (or attach it persistently to an API key / endpoint so it is auto-injected).
{
"model": "grok-4.3",
"messages": [
{"role": "user", "content": "What's the weather in Nashville?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather for a given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. 'Nashville, TN' or 'London, UK'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature to return"
}
},
"required": ["location"]
}
},
"webhook": {
"url": "https://example.com/api/aifunction",
"key": "my-super-secret-verification-key",
"timeout_seconds": 30
}
}
]
}The webhook Extension
url(required): The HTTPS endpoint we will POST to. Must be publicly reachable.key(required): A secret value you choose. We will use this both as the bearer token (see Authentication below) and as the HMAC secret to sign the request body.timeout_seconds(optional, default 30): Maximum time we will wait for your endpoint to respond.
We only support HTTPS URLs for security.
Webhook Request Payload
We send a POST request with Content-Type: application/json.
{
"tool_call_id": "call_abc123def456",
"name": "get_current_weather",
"arguments": {
"location": "Nashville, TN",
"unit": "fahrenheit"
},
"context": {
"user_id": "usr_1234567890",
"api_key_id": "key_9876543210",
"request_id": "req_abcdef123456",
"model": "grok-4.3"
}
}Field Descriptions
tool_call_id: The unique ID the model assigned to this particular tool call. You should return this (or we will match it) when providing the result.name: The name of the tool as defined in your tool schema.arguments: The arguments the model chose, already parsed as a JSON object (matching yourparametersschema).context.user_id: The Hypervize user who initiated the request.context.api_key_id: The specific API key used for this conversation.context.request_id: A unique identifier for this top-level inference request (useful for logging and tracing).context.model: The model the user is talking to.
Authentication note: We authenticate the original inference request using your Hypervize API key (the Authorization: Bearer hvz_live_... header). We only invoke a webhook for a tool that was supplied in an authenticated request from that same key. Your endpoint can therefore trust that the call represents legitimate activity initiated by you.
To let you cryptographically verify that the request really came from Hypervize and has not been tampered with or replayed, we use your key in two ways:
- We include it in the
Authorization: Bearer <your-key>header (quick rejection of unauthorized callers). - We compute an HMAC-SHA256 signature of the request and send it in
X-Hypervize-Signature: t=<unix-timestamp>,v1=<hex-signature>.
The signature is calculated as:
HMAC-SHA256(key, "t=" + timestamp + "." + rawRequestBody)Recommended validation on your endpoint (see the complete code example below for a working implementation):
- Check that the
Authorizationheader equalsBearer <your-key>. - Parse the
X-Hypervize-Signatureheader to extract the timestamp (t=) and the hex signature (v1=). - Reject the request if the timestamp is more than ~5 minutes old (replay protection).
- Recompute
HMAC-SHA256(your-key, "t=" + timestamp + "." + rawBody)exactly. - Use a constant-time comparison (e.g. Node's
crypto.timingSafeEqual) to compare your computed hex signature against the one in the header. - Only if both checks pass, process the request.
We always use HTTPS. This scheme is modeled on common webhook verification patterns (e.g. Stripe). A full working example is provided in the "Complete Example" section below.
Headers we send:
-
Content-Type: application/json -
User-Agent: Hypervize-Tool-Executor/1.0 -
X-Hypervize-Request-ID: Same ascontext.request_id -
Authorization: Bearer <your-key> -
X-Hypervize-Signature: t=<unix-timestamp>,v1=<hex-encoded-hmac-sha256>The value is computed as
HMAC-SHA256(your-key, "t=" + timestamp + "." + rawRequestBody). See the Authentication note below for the full recommended validation steps (including replay protection).
Webhook Response Format
Your endpoint must respond with a JSON object (HTTP 200). We support two equivalent shapes:
Simple string result (recommended for most cases)
{
"content": "The current weather in Nashville, TN is 72°F and sunny with clear skies."
}The value of content becomes the tool result message sent back to the model.
Structured result
{
"result": {
"temperature": 72,
"unit": "fahrenheit",
"condition": "sunny",
"humidity": 45,
"wind_speed": 5
}
}We will serialize the result object (usually via JSON.stringify) and use it as the tool result content.
Error response
{
"error": "Unable to fetch weather data: external API rate limit exceeded. Please try again later."
}If your response contains an error field (or returns a non-2xx status), we treat the value as the tool result. The model will see the error and can decide how to respond to the user.
Timeouts: If your webhook does not respond within the configured timeout_seconds (default 30), we treat it as an error and pass an appropriate message back to the model.
Complete Example (Vercel / Next.js)
Here is a complete, copy-pasteable example of a webhook endpoint that performs the full recommended security checks, including a reusable helper for the HMAC signature verification with constant-time comparison.
// app/api/aifunction/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhookSignature(
rawBody: string,
signatureHeader: string | null,
key: string
): boolean {
if (!signatureHeader) return false;
// Parse "t=<timestamp>,v1=<hex-signature>"
const match = signatureHeader.match(/t=(\d+),v1=([a-f0-9]+)/);
if (!match) return false;
const [, timestamp, receivedSignature] = match;
// Replay protection: reject requests older than 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
return false;
}
// Reconstruct the signed payload exactly as we did on our side
const signedPayload = `t=${timestamp}.${rawBody}`;
// Compute HMAC-SHA256 using the key you registered
const expectedSignature = createHmac('sha256', key)
.update(signedPayload, 'utf8')
.digest('hex');
// Constant-time comparison to prevent timing attacks
try {
return timingSafeEqual(
Buffer.from(receivedSignature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
} catch {
return false;
}
}
export async function POST(request: NextRequest) {
const rawBody = await request.text();
const body = JSON.parse(rawBody);
const authHeader = request.headers.get('authorization');
const signatureHeader = request.headers.get('x-hypervize-signature');
const expectedKey = 'my-super-secret-verification-key'; // the one you put in the "key" field
// 1. Quick bearer check
if (authHeader !== `Bearer ${expectedKey}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 2. Signature verification (recommended)
if (!verifyWebhookSignature(rawBody, signatureHeader, expectedKey)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const { tool_call_id, name, arguments: args, context } = body;
console.log(`Tool call ${tool_call_id} for ${name} from user ${context.user_id}`);
if (name === 'get_current_weather') {
// Your real logic here (call a weather API, query DB, etc.)
const { location, unit = 'fahrenheit' } = args;
// Fake response for demo
const temp = unit === 'fahrenheit' ? 72 : 22;
const result = `The current weather in ${location} is ${temp}°${unit[0].toUpperCase()} and sunny.`;
return NextResponse.json({
content: result,
});
}
return NextResponse.json({
error: `Unknown tool: ${name}`,
}, { status: 400 });
}Your endpoint must be reachable over the public internet (or via a tunnel during development).
Using the Feature
- Include one or more tools with the
webhookextension when calling/api/chat/completions(or the equivalent dedicated endpoint path). - Make sure the user has tool calling enabled (via the UI or key attachments) so the managed loop activates.
- Send a message that would benefit from your tool. The model will call it, we will invoke your webhook, and the final response will incorporate the result.
You can mix Hypervize-managed tools and your own webhook tools in the same request. The model can call any combination.
Best Practices & Limitations
- Keep webhooks fast: Aim for sub-second responses when possible. Long-running work should be started asynchronously and the initial result should indicate that (e.g., "Job started, polling...").
- Idempotency: Tool calls can be retried in some edge cases. Design your functions to be idempotent where it makes sense.
- Security: Use HTTPS. The
keyis required. It is used for both theAuthorization: Bearer <key>quick check and as the secret for theX-Hypervize-SignatureHMAC. Your endpoint must implement the full validation steps described in the Authentication note (including replay protection and constant-time comparison) before doing any work. We never expose your webhook URL or key to models or end users. - Timeouts & retries: We enforce a reasonable timeout (configurable per tool). Failed calls surface as errors to the model.
- Structured vs. text results: Prefer returning a clear
contentstring for best model compatibility. Use theresultobject when you want the model to receive structured data it can reason about. - Billing: Tool invocations via your webhooks are metered separately from token usage. You are responsible for any costs on your own infrastructure.
- Debugging: Use the
context.request_idto correlate logs between your webhook and the Hypervize request logs (available in the dashboard).
Raw Tool Calling Escape Hatch
If you prefer to handle tool execution yourself (client-side in the browser, in your own backend loop, etc.), simply do not register a webhook for that tool, or use the raw/non-managed path. In that case we will surface the tool_calls objects on the stream exactly as a standard OpenAI-compatible provider would, and you become responsible for sending follow-up messages containing the tool results.
Feedback & Roadmap
This webhook mechanism is the primary way to bring your own server-side tools into Hypervize's managed experience today. We plan to add:
- Pre-registered tools (so you don't have to send the full definition on every request)
- Richer result types (images, files, citations)
- Better observability for webhook calls inside the dashboard
- Support for long-running / async tools with polling
If you have feedback on the payload shapes, auth model, or ergonomics, please reach out.
This document is the current source of truth for payload formats and behavior.