Build custom tools for AI agents
A custom tool is your HTTPS endpoint wired into the agent’s tool list. The LLM decides to call it the same way it decides to call a built-in (lookup_contacts, send_sms) or a marketplace tool — but the run forwards
the call to a webhook you own, on your infrastructure. Custom tools are
the way agents read your order system, write into your CRM, or reach any
internal API without you shipping code into the runtime. This guide walks
the create–test–attach–rotate lifecycle and the failure modes to guard.
The endpoint paths below are relative. Send them against
https://api.orbit.devotel.io/api/v1. The dashboard surface for everything
below lives at /agents/custom-tools.
1. What a custom tool is (and isn’t)
Every tool call the LLM emits has a name, a JSON-Schema input contract the model fills, and an executor that runs it. The three classes differ only in who owns the executor:- Built-in / marketplace tools — the executor is Orbit-hosted code. You flip them on per agent; you can’t edit what they do.
- Custom tools — the executor is an HTTPS webhook you register in
the tenant-wide registry. You supply the URL, the schema, an optional
HMAC secret, and a timeout. Orbit validates the LLM’s args against your
schema, POSTs
{ tool_name, args, agent_id, agent_run_id, conversation_id }to the webhook, and returns the response body to the agent as the tool result. - Per-agent attach — the registry is tenant-wide; each agent opts into
specific tools from its Tools tab on the agent detail page
(
/agents/:id). The page header cites the Twilio Functions-vs-Service separation: the registry holds the function definition; the agent chooses which of them are callable. Deleting a registry item strips it from every agent list.
2. Create the registry item
One tool = one POST to the registry. Required: a slug, a description (the LLM uses it to decide when to call), the executor HTTPS URL. Optional: schema (recommended), secret, timeout, confirmation mode.name— lowercase snake_case, 3–64 chars starting with a letter, and immutable (it’s the id the LLM sees; renaming would break trained prompts). Verified:/^[a-z][a-z0-9_]{1,62}[a-z0-9]$/.json_schema— an object-root JSON-Schema withproperties+required(works for both Claude and OpenAI tool-calling), ≤ 32 KB.executor_url—https://only. Private IPs, link-local, and cloud metadata endpoints are rejected at write and again at dispatch (DNS re-resolved on every call).executor_secret— if set, every dispatch carries an HMAC signature. Min 8 chars; encrypted at rest and never returned by any GET. The response exposes onlyexecutor_secret_set: true.timeout_ms— 1–60 s; 30 s default. Must stay under the agent turn’s own deadline.confirmation—"never"(default) executes immediately;"always"parks each call in the approval queue for a human.
Roles: create/update/delete need
owner, admin, or developer; the
read list is fine for the dashboard session. Every mutation publishes a
tenant-wide invalidation so agents pick it up on their next turn rather
than on the runtime’s cache TTL.
3. Validate the schema and dry-fire before enabling
Create the tool withenabled: false (or flap the switch off in the
builder) until a live test passes. The builder dialog’s Test request
panel fires a real dispatch without persisting anything — hit it once with
a stub args object (the form can generate one from required):
{ tool_name, args, agent_run_id, test: true }; for a real call it’s
{ tool_name, args, agent_id, agent_run_id, conversation_id }. Return a
small body — it goes straight to the LLM as the tool result. Non-2xx,
timeouts, and unreachable hosts land in error with ok: false; the
result panel in the dashboard shows the same detail.
4. Attach it per agent (registry is tenant-wide)
The tool now exists for the whole tenant but is callable by no agent until you opt in on that agent’s Tools tab (/agents/:id). Per-agent
attach is a separate GET/PATCH on the agent — it controls
config.custom_tool_ids, an id list the runtime resolves at executor
build. Toggle enabled in the registry to cut the tool off for every
agent at once (the per-agent list still names it; the runtime skips it).
Because the registry is tenant-wide you can stage a tool in one dashboard,
attach it to your test agent, and promote by flipping enabled: true —
the same separation the page header calls out between the definition and
the per-service agent’s allowlist.
5. Rotate the HMAC secret without downtime
The webhook rotation playbook applies one tool at a time: overlap the old and new secrets for a short window, then drop the old.- Deploy the receiver change so both secrets verify (
validSecrets = [new, old]in the verifier below). - PATCH the tool with the new
executor_secret. - Wait one drain window (60 s, the longest timeout you allow).
- Cut the old secret from the accept-list and redeploy. To revoke
entirely, PATCH
executor_secret: null— further dispatches go out unsigned; a receiver that requires a signature then rejects them.
name, so rotate during business
hours without touching the slug the prompts trained on.
6. Receive and verify the payload (receiver reference)
Each dispatch is a POST withX-Devotel-Tool-Signature: t=<unix-seconds>,v1=<sha256-hmac> over
<unix-seconds>.<raw-body>, exactly the Stripe-style scheme so Stripe
verifier libraries work. Overlap-friendly verifier:
7. Failure modes and retries
- Schema mismatch — the model’s args must satisfy
requiredin your schema. A sloppy schema (missingrequired) makes the executor reject the dispatch before your webhook is ever touched. Re-test after every schema edit. - Non-2xx — the agent sees
ok: falsewith yourstatusand the same body you returned, so treat 4xx/5xx as a deterministic refusal. The agent’s spoken failure text (if you configuredmessages) covers this. - Timeout —
timeout_mscaps each call; a webhook that takes longer tells the agent the tool errored today. Set it well under the 60 s ceiling and keep your handler’s own work inside that budget. on_error— registry fields let you pickspeak(say a safe tenant-authored line) orfallback_tool(chain to another registry tool) instead of surfacing a raw failure to the caller.- Confirmation queue —
confirmation: "always"means a pending approval, not an error; see Tool approvals. - Revocation — everyone with
owner/admin/developercan delete; the cascade strips the id from every agent’scustom_tool_idsin the same mutation, so no dangling reference.
Related reading
- Tool approvals — page the approval pause queue when a tool is gated.
- Agents from a prompt — preset tools; this guide covers the registry you build yourself.
- Buy numbers — endpoint docs for voice tools the agent uses alongside custom tools.