Skip to main content

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 with properties + required (works for both Claude and OpenAI tool-calling), ≤ 32 KB.
  • executor_urlhttps:// 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 only executor_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 with enabled: 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):
The payload your endpoint receives for a test is { 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.
  1. Deploy the receiver change so both secrets verify (validSecrets = [new, old] in the verifier below).
  2. PATCH the tool with the new executor_secret.
  3. Wait one drain window (60 s, the longest timeout you allow).
  4. 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.
A PATCH can change every field except 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 with X-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 required in your schema. A sloppy schema (missing required) makes the executor reject the dispatch before your webhook is ever touched. Re-test after every schema edit.
  • Non-2xx — the agent sees ok: false with your status and the same body you returned, so treat 4xx/5xx as a deterministic refusal. The agent’s spoken failure text (if you configured messages) covers this.
  • Timeouttimeout_ms caps 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 pick speak (say a safe tenant-authored line) or fallback_tool (chain to another registry tool) instead of surfacing a raw failure to the caller.
  • Confirmation queueconfirmation: "always" means a pending approval, not an error; see Tool approvals.
  • Revocation — everyone with owner/admin/developer can delete; the cascade strips the id from every agent’s custom_tool_ids in the same mutation, so no dangling reference.
  • 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.