Skip to content

Inbound webhooks

A vendor that delivers events to a webhook URL (a payment processor, a campaign platform, a CRM) cannot carry a NimbleBrain token, so it cannot call your server directly. Declaring a hooks entry asks the host for a URL that it will accept those deliveries on and forward to you.

hooks is a capability of the ai.nimblebrain/host block. The host reads it from your server’s connectors catalog entry only. A hooks block in an MCPB bundle manifest has no effect, because the route and registration tool decide where a delivery is sent, and that has to come from metadata the operator published.

"_meta": {
"ai.nimblebrain/host": {
"host_version": "1.0",
"hooks": [
{
"vendor": "acme",
"route": "/ingest/acme",
"register_tool": "set_webhook_url",
"description": "Order events: created, paid, refunded"
}
]
}
}
Field Type Required Description
vendor string Yes Slug for the vendor, lowercase alphanumeric with internal hyphens. One entry per vendor; a duplicate is dropped.
route string Yes Absolute path on your own server that deliveries are forwarded to. Must stay on your origin: no //host, no .., no backslash, no fragment, no scheme.
register_tool string Yes A tool on your server that the host calls with { vendor, url }, both strings, to hand you the minted URL.
description string No What the stream carries. Shown to operators; the host never acts on it.
header_renames object No { from: to } header renames applied before the forward. See signature headers below.
  1. When your server comes up in a workspace, the host mints a URL for exactly one (workspace, connector, vendor) and calls your register_tool with it. Store it, and register it with the vendor.
  2. On delivery, the host checks the URL, then forwards the request to your route, body bytes unchanged and the vendor’s own headers passed through. It arrives with the same x-tenant-id / x-workspace-id identity headers a tool call gets.
  3. Your server answers, and the host returns your status code to the vendor unchanged. Answer 2xx once you have durably recorded the delivery; answer 5xx only when you want the vendor to retry.

The host never parses a delivery body. Signature verification, parsing, idempotency, and state are entirely yours. Vendors redeliver, so record each delivery under the vendor’s own event id and treat a repeat as a no-op.

Provisioning runs every time your connector’s connection comes up, not once at install. That covers a fresh install, a host restart, and an interactive OAuth flow that finishes long after the install returned. It mints only what is missing. If your register_tool call itself errors, the host keeps the registration; a rotation or a reinstall hands you the URL again.

Your register_tool must accept { vendor, url }

Section titled “Your register_tool must accept { vendor, url }”

The host checks this against your advertised tools/list when the connection comes up. If the tool is missing, or does not accept both arguments as strings, no URL is minted for any of your streams and the operator is shown a warning naming the declaration. The connector still installs, and its tools still work. A stream whose registration tool is wrong would otherwise fail silently, months later, at a vendor nobody is watching.

The check re-runs every time the connection is re-established and every time your advertised tool set changes, so fixing the manifest and reconnecting provisions the streams without a reinstall.

Your tools do not have to be ready the moment the host connects. A server that advertises nothing yet is not a contract failure: nothing is minted, nothing is reported wrong, and the streams provision as soon as your tool list appears.

@mcp.tool()
def set_webhook_url(vendor: str, url: str) -> dict:
# Store the URL in your own per-workspace config, then register it with
# the vendor. Called again with a new URL whenever it is rotated.
...

The host calls register_tool itself. That call is not subject to admin_tools, so you do not need to, and should not, name it there.

Your vendor’s own headers reach you (Stripe-Signature, X-Acme-Signature, whatever it uses), so you can verify the delivery came from the vendor and not merely from someone holding the URL. Verify them. Possession of the URL proves only that the host should forward the request to you for this workspace; it does not prove who sent it.

One class of header does not reach you: anything a caller could use to assert identity. That is Authorization, X-Api-Key, X-Tenant-Id, X-Workspace-Id, X-User-Id, and everything under the reserved X-NB-* prefix. These are stripped, because the only identity on a forwarded delivery must be the one the platform verified. If your vendor signs on one of those headers, declare a rename. Cookie never reaches you either, and cannot be renamed: a browser posting to the URL from the platform’s own origin attaches the user’s session cookie, and no vendor signs with it. A rename whose target is itself a stripped header is ignored.

{ "vendor": "acme", "route": "/ingest/acme", "register_tool": "set_webhook_url",
"header_renames": { "authorization": "x-acme-signature" } }

A workspace admin sees each URL, the connector and route it feeds, and when it was last rotated, under Settings → Webhooks. Rotating mints a new URL and calls your register_tool with it. The previous URL keeps working for 24 hours so in-flight redeliveries land. Your register_tool should therefore replace the URL it holds and re-register with the vendor, not assume it is called once.

A URL does not expire on its own. It stops working when it is rotated out, or when the connector is uninstalled, in which case the host retires your registrations itself.

Body size 256 KiB; larger deliveries are refused with 413 before reaching you
Method POST only; any other method gets 405
Unknown or retired URL 404 with an empty body, the same answer for every reason, so the URL space cannot be probed
Retries None from the host: a single forward attempt. Your vendor’s own retry, plus whatever reconciliation you poll for, is the durability story
Correlation The host adds no header of its own. An operator correlates a delivery to the URL it arrived on through the platform’s own logs, so you do not need to record one
  • Manifest Reference: the hooks field in the ai.nimblebrain/host block
  • Notifications: the other direction, your server telling the host about something it learned
  • Lifecycle: the install and uninstall events