Documentation

Hosted webhook URLs

A URL that holds deliveries while the machine sleeps; the machine still checks every signature.

What a hosted URL is

A skill's hosted URL is an address on this service, https://skillhook.dev/i/<key>, that a webhook sender can use instead of the machine's own URL. The cloud accepts the request for a machine that may be asleep, behind no public URL, or simply busy, keeps it sealed, and hands it over on the machine's next sync. There the delivery goes through the same pipeline as a direct webhook: the signature is verified with the skill's own secret, filters and deduplication apply, a job starts. The cloud never holds webhook secrets and never reads the body as anything but bytes. Providers that probe a URL with GET when it is configured get a short text answer saying which skill it is for.

Turning it on and off

In Skills & URLs, an admin turns a hosted URL on per skill and machine; the URL is shown there and copied to the sender together with the skill's secret, exactly as the machine's own URL would be. From the API and MCP, enable_hosted_url and disable_hosted_url need a fleet:admin key. Turned off, the URL answers 404 until it is turned on again, with a new URL.

The URL is a capability

Whoever has the URL can queue a delivery for that skill on that machine, so it is treated like a secret: stored as a peppered hash for lookup and sealed so members can see it again, shown to members and admins (get_hosted_url), and never in a listing. list_hosted_urls shows the key's prefix, whether the URL is on, what it received and how many deliveries wait for the machine. It stays a capability to queue, not to run: the machine still checks every delivery's signature with the secret that never left it, and a forged delivery is rejected there and shows up in Deliveries with the reason.

Rotation

Turning a hosted URL on for a skill that already has one makes a new URL, and the previous one stops working at once; update the sender. Rotate when a URL has leaked or when a sender is retired. Turning off and on again is the same operation.

Limits and answers

A hosted URL accepts 120 deliveries a minute, an organisation's URLs 600 a minute together, a body of at most 1,048,576 bytes, and at most 10,000 deliveries may wait for one machine. Deliveries accepted at hosted URLs count towards the plan's monthly hosted deliveries (see Plans and limits); deliveries straight to a machine never do.

StatusCodeMeaning
202queuedAccepted and sealed; the body carries the delivery's id. The machine collects it on its next sync.
200challengeA Slack url_verification request: the challenge is answered at once and nothing is stored.
404unknown_hookNo such URL, or the skill's hosted URL is turned off. A new URL is made when it is turned on again.
410goneThe machine behind the URL was disconnected. Senders that unsubscribe on 410 are right to.
413payload_too_largeThe body is larger than 1,048,576 bytes, what a machine accepts at its own URL by default.
429rate_limitedMore than 120 deliveries a minute at this URL, or 600 across the organisation's URLs; Retry-After says when.
429backlog_full10,000 deliveries already wait for this machine; it has to collect them first.
503unavailableOur trouble, worth a retry (Retry-After): never a 404 or 410 that would make a sender stop sending.

The 72-hour hold

The method, query string, headers and body of an accepted delivery are sealed with AES-256-GCM and stored until the machine acknowledges them on a sync, then deleted. A delivery the machine has not collected within 72 hours expires and its sealed body is deleted; the delivery record stays, marked expired. Deliveries the machine received keep a record on both sides, with via: ingress and the hosted delivery's id on the machine's record.

Slack URL verification

When a Slack app's event subscription is saved, Slack posts a url_verification request and wants its challenge echoed back at once. A hosted URL answers it directly, so the subscription saves even while the machine sleeps; nothing is stored, and nothing secret is involved. Every other Slack event is held for the machine, which verifies X-Slack-Signature with the app's signing secret.

Headers that are dropped

The headers that describe the hop to this service, not the sender's request, are dropped before sealing, so the machine sees the request the sender made: host, connection, content-length, transfer-encoding, keep-alive, upgrade, expect, forwarded, via, x-real-ip, cf-connecting-ip, true-client-ip, and every x-vercel-* and x-forwarded-* header. Signature headers, delivery ids and the content type pass through untouched, which is what the machine verifies against.

Replaying hosted deliveries

Once the machine has run a hosted delivery, it is a delivery like any other: it appears in Deliveries with via: ingress, and replay_delivery runs it again through the skill as it is now. A delivery that was rejected needs force (its checks are skipped on replay); skip_filters ignores the skill's when filters. list_deliveries {via: "ingress"} lists only hosted ones, {via: "http"} only those sent straight to the machine. A delivery the machine never collected (expired, or the machine disconnected) cannot be replayed; ask the sender to send it again. Webhook history and replay has the search, the replay on another machine or skill, and the CLI recipe for sending a stored webhook anywhere.