← Discover MCPs and Agents
h
MCPAI & MLGitHub

hookdeck-cli

CLI for Hookdeck: forward webhooks to localhost (ngrok alternative), manage and query Event Gateway resources (sources, connections, destinations, events), run the MCP server for AI agents. Free for dev.

Links

README

From the repo.

Hookdeck CLI

slack-badge

Using the Hookdeck CLI, you can forward your events (e.g. webhooks) to your local web server with unlimited free and permanent event URLs. Your event history is preserved between sessions and can be viewed, replayed, or used for testing by you and your teammates.

Hookdeck CLI is compatible with most of Hookdeck's features, such as filtering and fan-out delivery. You can use Hookdeck CLI to develop or test your event (e.g. webhook) integration code locally.

You can also manage Hookdeck Event Gateway resources—sources, destinations, connections, events, transformations—from the CLI. For AI and agent workflows, the Event Gateway MCP server (hookdeck gateway mcp) exposes these capabilities as tools in MCP-compatible clients (e.g. Cursor, Claude).

Although it uses a different approach and philosophy, it's a replacement for ngrok and alternative HTTP tunnel solutions.

Hookdeck for development is completely free, and we monetize the platform with our production offering.

For a complete reference of all commands and flags, see REFERENCE.md.

Table of contents

Quick links: Local development (Listen) · Resource management (CLI) / Manage connections · AI / agent integration (Event Gateway MCP)

https://github.com/user-attachments/assets/7a333c5b-e4cb-45bb-8570-29fafd137bd2

Installation

Hookdeck CLI is available for macOS, Windows, and Linux for distros like Ubuntu, Debian, RedHat, and CentOS.

NPM

Hookdeck CLI is distributed as an NPM package:

npm install hookdeck-cli -g

To install a beta (pre-release) version:

npm install hookdeck-cli@beta -g

macOS

Hookdeck CLI is available on macOS via Homebrew in homebrew-core:

brew install hookdeck

New stable versions are picked up automatically by Homebrew's autobump after each release — brew upgrade will pull them in.

To install a beta (pre-release) version from our tap:

brew install hookdeck/hookdeck/hookdeck-beta

[!NOTE] When HOMEBREW_REQUIRE_TAP_TRUST becomes the default in Homebrew 5.2.0 / 6.0.0, installing the beta formula from a third-party tap will require an explicit trust step:

brew trust --formula hookdeck/hookdeck/hookdeck-beta

The stable hookdeck formula lives in homebrew-core and is not affected.

Windows

Hookdeck CLI is available on Windows via the Scoop package manager:

scoop bucket add hookdeck https://github.com/hookdeck/scoop-hookdeck-cli.git
scoop install hookdeck

To install a beta (pre-release) version:

scoop install hookdeck-beta

Linux Or without package managers

To install the Hookdeck CLI on Linux without a package manager:

  1. Download the latest linux tar.gz file from https://github.com/hookdeck/hookdeck-cli/releases/latest
  2. Unzip the file: tar -xvf hookdeck_X.X.X_linux_amd64.tar.gz
  3. Run the executable: ./hookdeck

For beta (pre-release) versions, download the .deb or .rpm packages from the GitHub releases page (look for releases marked as "Pre-release").

Docker

The CLI is also available as a Docker image: hookdeck/hookdeck-cli.

docker run --rm -it hookdeck/hookdeck-cli version
hookdeck version x.y.z (beta)

To use a specific version (including beta releases), specify the version tag:

docker run --rm -it hookdeck/hookdeck-cli:v1.2.3-beta.1 version

Note: Beta releases do not update the latest tag. Only stable releases update latest.

If you want to login to your Hookdeck account with the CLI and persist credentials, you can bind mount the ~/.config/hookdeck directory:

docker run --rm -it -v $HOME/.config/hookdeck:/root/.config/hookdeck hookdeck/hookdeck-cli login

Then you can listen on any of your sources. Don't forget to use host.docker.internal to reach a port on your host machine, otherwise that port will not be accessible from localhost inside the container.

docker run --rm -it -v $HOME/.config/hookdeck:/root/.config/hookdeck hookdeck/hookdeck-cli listen http://host.docker.internal:1234

Usage

Installing the CLI provides access to the hookdeck command.

hookdeck [command]

# Run `--help` for detailed information about CLI commands
hookdeck [command] help

Commands

Login

Login with your Hookdeck account. This will typically open a browser window for authentication.

hookdeck login

If you are in an environment without a browser (e.g., a TTY-only terminal), you can use the --interactive (or -i) flag to log in by pasting your API key:

hookdeck login --interactive

To authenticate with a CLI client key from the Hookdeck product (no browser step when the key is already associated with your account and project):

hookdeck login --cli-key <key>

The CLI validates the key via the API and writes your config, replacing a guest Console profile if one exists. For example, Hookdeck may show this command during Event Gateway onboarding or when authorizing the CLI as a Console destination.

Guest sandbox upgrade (keeping Console data) requires hookdeck login without --cli-key, not product copy-paste keys. If you do not log in, a temporary guest account is created when you run commands such as hookdeck listen.

Listen

Start a session to forward your events to an HTTP server.

hookdeck listen <port-or-URL> <source-alias?> <connection-query?> [flags]

Flags:
  --path string             Sets the path to which events are forwarded (e.g., /webhooks or /api/stripe)
  --output string           Output mode: interactive (full UI), compact (simple logs), quiet (only fatal errors) (default "interactive")
  --max-connections int     Maximum concurrent connections to local endpoint (default: 50, increase for high-volume testing)
  --filter-body string      Filter events by request body using Hookdeck filter syntax (JSON)
  --filter-headers string   Filter events by request headers using Hookdeck filter syntax (JSON)
  --filter-query string     Filter events by query parameters using Hookdeck filter syntax (JSON)
  --filter-path string      Filter events by request path using Hookdeck filter syntax (JSON)
  --cli-key string          Authenticate with a user-scoped CLI key instead of the stored login
  --api-key string          Authenticate with a project-scoped key instead of the stored login

By default listen uses the credentials saved by hookdeck login. To authenticate a single invocation without logging in first — for example in CI or when switching accounts — pass a key directly:

# User-scoped CLI key (created by `hookdeck login`; can access all your projects)
$ hookdeck listen 3000 stripe --cli-key <your-cli-key>

# Project-scoped key (e.g. a Project API key or a key from `hookdeck ci`)
$ hookdeck listen 3000 stripe --api-key <your-project-api-key>

Which key you hold decides what the CLI can do. The supported ways to supply one are hookdeck login for a CLI key, hookdeck login --cli-key to paste an existing one, and hookdeck ci --api-key for a project API key:

KeyWhere it comes fromReachproject list / project use
CLI keyhookdeck login, or hookdeck login --cli-keyevery project in every organization you belong toyes
Project API keydashboard, or hookdeck ci --api-keythe one project it belongs tono
Organization API keydashboardits organization's projects, given the projects.read scopeno

Only a CLI key can list or switch projects. The others are scoped below the level that question is asked at, so hookdeck project list answers this credential is scoped to a single project, and you need hookdeck login for account-wide access.

hookdeck ci --api-key takes a project API key specifically. It exchanges it for a project-scoped CLI key, which is why keys minted that way cannot list projects either. An organization API key is rejected by hookdeck ci and by every other CLI sign-in path, so it cannot be used to authenticate the CLI at all — use it against the REST API directly.

See also Credential Types for how each is stored.

The Event Gateway routes events received for a given source (e.g. Shopify, GitHub) to a destination via a connection. hookdeck listen is a standalone command that works with whichever product you're authenticated with — Hookdeck Console or the Event Gateway — receiving events for a given connection and forwarding them to your localhost at the specified port or any valid URL.

Each source is assigned an Event URL, which you can use to receive events. When starting with a fresh account, the CLI will prompt you to create your first source. Each CLI process can listen to one source at a time.

The port-or-URL param is mandatory, events will be forwarded to http://localhost:$PORT/$DESTINATION_PATH when inputing a valid port or your provided URL.

Interactive Mode

The default interactive mode uses a full-screen TUI (Terminal User Interface) with an alternative screen buffer, meaning your terminal history is preserved when you exit. The interface includes:

  • Connection Header: Shows your sources, webhook URLs, and connection routing
    • Auto-collapses when the first event arrives to save space
    • Toggle with i to expand/collapse connection details
  • Event List: Scrollable history of all received events (up to 1000 events)
    • Auto-scrolls to show latest events as they arrive
    • Manual navigation pauses auto-scrolling
  • Status Bar: Shows event details and available keyboard shortcuts
  • Event Details View: Full request/response inspection with headers and body

Interactive Keyboard Shortcuts

While in interactive mode, you can use the following keyboard shortcuts:

  • ↑ / ↓ or k / j - Navigate between events (select different events)
  • i - Toggle connection information (expand/collapse connection details)
  • r - Retry the selected event
  • o - Open the selected event in the Hookdeck dashboard
  • d - Show detailed request/response information for the selected event (press d or ESC to close)
    • When details view is open: ↑ / ↓ scroll through content, PgUp / PgDown for page navigation
    • Press C to copy the complete request, H for request headers, or B for the request body; off-screen content is included
  • q - Quit the application (terminal state is restored)
  • Ctrl+C - Also quits the application

The selected event is indicated by a > character at the beginning of the line. All actions (retry, open, details) work on the currently selected event, not just the latest one. These shortcuts are displayed in the status bar at the bottom of the screen.

Note: Copying to the clipboard works out of the box on macOS and Windows. On Linux and other BSD/Unix systems it requires either xclip or xsel to be installed; without one of them the copy shortcuts report an error.

Listen to all your connections for a given source

The second param, source-alias is used to select a specific source to listen on. By default, the CLI will start listening on all eligible connections for that source.

$ hookdeck listen 3000 shopify

●── HOOKDECK CLI ──●

Listening on 1 source • 2 connections • [i] Collapse

Shopify Source
│  Requests to → https://events.hookdeck.com/e/src_DAjaFWyyZXsFdZrTOKpuHnOH
├─ Forwards to → http://localhost:3000/webhooks/shopify/inventory (Inventory Service)
└─ Forwards to → http://localhost:3000/webhooks/shopify/orders (Orders Service)

💡 Open dashboard to inspect, retry & bookmark events: https://dashboard.hookdeck.com/events/cli?team_id=...

Events • [↑↓] Navigate ──────────────────────────────────────────────────────────

2025-10-12 14:32:15 [200] POST http://localhost:3000/webhooks/shopify/orders (23ms) → https://dashboard.hookdeck.com/events/evt_...
> 2025-10-12 14:32:18 [200] POST http://localhost:3000/webhooks/shopify/inventory (45ms) → https://dashboard.hookdeck.com/events/evt_...

───────────────────────────────────────────────────────────────────────────────
> ✓ Last event succeeded with status 200 | [r] Retry • [o] Open in dashboard • [d] Show data

Listen to multiple sources

source-alias can be a comma-separated list of source names (for example, stripe,shopify,twilio) or '*' (with quotes) to listen to all sources.

$ hookdeck listen 3000 '*'

●── HOOKDECK CLI ──●

Listening on 3 sources • 3 connections • [i] Collapse

stripe
│  Requests to → https://events.hookdeck.com/e/src_DAjaFWyyZXsFdZrTOKpuHn01
└─ Forwards to → http://localhost:3000/webhooks/stripe (cli-stripe)

shopify
│  Requests to → https://events.hookdeck.com/e/src_DAjaFWyyZXsFdZrTOKpuHn02
└─ Forwards to → http://localhost:3000/webhooks/shopify (cli-shopify)

twilio
│  Requests to → https://events.hookdeck.com/e/src_DAjaFWyyZXsFdZrTOKpuHn03
└─ Forwards to → http://localhost:3000/webhooks/twilio (cli-twilio)

💡 Open dashboard to inspect, retry & bookmark events: https://dashboard.hookdeck.com/events/cli?team_id=...

Events • [↑↓] Navigate ──────────────────────────────────────────────────────────

2025-10-12 14:35:21 [200] POST http://localhost:3000/webhooks/stripe (12ms) → https://dashboard.hookdeck.com/events/evt_...
2025-10-12 14:35:44 [200] POST http://localhost:3000/webhooks/shopify (31ms) → https://dashboard.hookdeck.com/events/evt_...
> 2025-10-12 14:35:52 [200] POST http://localhost:3000/webhooks/twilio (18ms) → https://dashboard.hookdeck.com/events/evt_...

───────────────────────────────────────────────────────────────────────────────
> ✓ Last event succeeded with status 200 | [r] Retry • [o] Open in dashboard • [d] Show data

Listen to a subset of connections

The 3rd param, connection-query specifies which connection with a CLI destination to adopt for listening. By default, the first connection with a CLI destination type will be used. If a connection with the specified name doesn't exist, a new connection will be created with the passed value. The connection query is checked against the connection name, alias, and the path values.

$ hookdeck listen 3000 shopify orders

●── HOOKDECK CLI ──●

Listening on 1 source • 1 connection • [i] Collapse

Shopify Source
│  Requests to → https://events.hookdeck.com/e/src_DAjaFWyyZXsFdZrTOKpuHnOH
└─ Forwards to → http://localhost:3000/webhooks/shopify/orders (Orders Service)

💡 Open dashboard to inspect, retry & bookmark events: https://dashboard.hookdeck.com/events/cli?team_id=...

Events • [↑↓] Navigate ──────────────────────────────────────────────────────────

> 2025-10-12 14:38:09 [200] POST http://localhost:3000/webhooks/shopify/orders (27ms) → https://dashboard.hookdeck.com/events/evt_...

───────────────────────────────────────────────────────────────────────────────
> ✓ Last event succeeded with status 200 | [r] Retry • [o] Open in dashboard • [d] Show data

Changing the path events are forwarded to

The --path flag sets the path to which events are forwarded.

$ hookdeck listen 3000 shopify orders --path /events/shopify/orders

●── HOOKDECK CLI ──●

Listening on 1 source • 1 connection • [i] Collapse

Shopify Source
│  Requests to → https://events.hookdeck.com/e/src_DAjaFWyyZXsFdZrTOKpuHnOH
└─ Forwards to → http://localhost:3000/events/shopify/orders (Orders Service)

💡 Open dashboard to inspect, retry & bookmark events: https://dashboard.hookdeck.com/events/cli?team_id=...

Events • [↑↓] Navigate ──────────────────────────────────────────────────────────

> 2025-10-12 14:40:23 [200] POST http://localhost:3000/events/shopify/orders (19ms) → https://dashboard.hookdeck.com/events/evt_...

───────────────────────────────────────────────────────────────────────────────
> ✓ Last event succeeded with status 200 | [r] Retry • [o] Open in dashboard • [d] Show data

Controlling output verbosity

The --output flag controls how events are displayed. This is useful for reducing resource usage in high-throughput scenarios or when running in the background.

Available modes:

  • interactive (default) - Full-screen TUI with alternative screen buffer, event history, navigation, and keyboard shortcuts. Your terminal history is preserved and restored when you exit.
  • compact - Simple one-line logs for all events without interactive features. Events are appended to your terminal history.
  • quiet - Only displays fatal connection errors (network failures, timeouts), not HTTP errors

All modes display connection information at startup and a connection status message.

Examples:

# Default - full interactive UI with keyboard shortcuts
$ hookdeck listen 3000 shopify

# Simple logging mode - prints all events as one-line logs
$ hookdeck listen 3000 shopify --output compact

# Quiet mode - only shows fatal connection errors
$ hookdeck listen 3000 shopify --output quiet

Compact mode output:

Listening on
shopify
└─ Forwards to → http://localhost:3000

Connected. Waiting for events...

2025-10-08 15:56:53 [200] POST http://localhost:3000 (45ms) → https://...
2025-10-08 15:56:54 [422] POST http://localhost:3000 (12ms) → https://...

Quiet mode output:

Listening on
shopify
└─ Forwards to → http://localhost:3000

Connected. Waiting for events...

2025-10-08 15:56:53 [ERROR] Failed to POST: connection refused

Note: In quiet mode, only fatal errors are shown (connection failures, network unreachable, timeouts). HTTP error responses (4xx, 5xx) are not displayed as they are valid HTTP responses.

Filtering events

The CLI supports filtering events using Hookdeck's filter syntax. Filters allow you to receive only events that match specific conditions, reducing noise and focusing on the events you care about during development.

Filter flags:

  • --filter-body - Filter events by request body content (JSON)
  • --filter-headers - Filter events by request headers (JSON)
  • --filter-query - Filter events by query parameters (JSON)
  • --filter-path - Filter events by request path (JSON)

All filter flags accept JSON using Hookdeck's filter syntax. You can use exact matches or operators like $exist, $gte, $lte, $in, etc.

Examples:

# Filter events by body content (only events with matching data)
hookdeck listen 3000 github --filter-body '{"action": "opened"}'

# Filter events with multiple conditions
hookdeck listen 3000 stripe --filter-body '{"type": "charge.succeeded"}' --filter-headers '{"x-stripe-signature": {"$exist": true}}'

# Filter using operators
hookdeck listen 3000 api --filter-body '{"amount": {"$gte": 100}}'

When filters are active, the CLI will display a warning message indicating which filters are applied. Only events matching all specified filter conditions will be forwarded to your local server.

Viewing and interacting with your events

Event logs for your CLI can be found at https://dashboard.hookdeck.com/cli/events. Events can be replayed or saved at any time.

Logout

Logout of your Hookdeck account and clear your stored credentials.

hookdeck logout

Skip SSL validation

When forwarding events to an HTTPS URL as the first argument to hookdeck listen (e.g., https://localhost:1234/webhook), you might encounter SSL validation errors if the destination is using a self-signed certificate.

For local development scenarios, you can instruct the listen command to bypass this SSL certificate validation by using its --insecure flag. You must provide the full HTTPS URL. This flag also applies to the periodic server health checks that the CLI performs.

This is dangerous and should only be used in trusted local development environments for destinations you control.

Example of skipping SSL validation for an HTTPS destination:

hookdeck listen --insecure https://<your-ssl-url-or-url:port>/ <source-alias?> <connection-query?>

Disable health checks

The CLI periodically checks if your local server is reachable and displays warnings if the connection fails. If these health checks cause issues in your environment, you can disable them with the --no-healthcheck flag:

hookdeck listen --no-healthcheck 3000 <source-alias?>

Version

Print your CLI version and whether or not a new version is available.

hookdeck version

Completion

Generate a shell completion script for the Hookdeck CLI. When installed via Homebrew or Scoop, completions are configured automatically.

To enable completions for the current shell session:

# bash
source <(hookdeck completion --shell bash)

# zsh
source <(hookdeck completion --shell zsh)

To install completions permanently, redirect the output to your shell's completion directory. See hookdeck completion --help for examples.

Running in CI

If you want to use Hookdeck in CI for tests or any other purposes, authenticate with a Project API key from the dashboard. The ci command exchanges it for a CLI client key stored in your config. It must be a project key: organization API keys are rejected, and the resulting CLI key is project-scoped, so it cannot list or switch projects.

$ hookdeck ci --api-key $HOOKDECK_API_KEY
Done! The Hookdeck CLI is configured in project MyProject

$ hookdeck listen 3000 shopify orders

HOOKDECK_API_KEY is read automatically, so you can skip the ci step entirely — if no credentials are stored, listen exchanges the Project API key for CLI credentials and saves them, then connects to your project:

$ export HOOKDECK_API_KEY="your-project-api-key"
$ hookdeck listen 3000 shopify orders

Authentication order is --cli-key, then stored credentials from hookdeck login or hookdeck ci, then HOOKDECK_API_KEY. A real stored login is never repointed by the environment — but a temporary guest profile is, so a machine that once ran listen without credentials still uses your project once the variable is set. Replacing a guest profile is announced on stderr and discards the link to that sandbox; unset HOOKDECK_API_KEY if you want to keep it.

Without a Project API key listen still falls back to a temporary guest account, which is convenient locally but has no delivery history, retries, or issue triggers. If you meant to use your own project, check that HOOKDECK_API_KEY is actually set in the shell running the command — a variable that is unset there expands to an empty string, and values in a .env file are not loaded automatically just because your application reads them.

Output without a terminal

listen defaults to --output interactive, a full-screen UI that needs a terminal. In CI, Docker, nohup, or an AI agent there is no terminal, so it automatically falls back to --output compact — plain, line-based logs suited to a log file:

$ hookdeck listen 3000 shopify orders
⏺ Ready! Forwarding events from Shopify Source to http://localhost:3000/webhooks/shopify/orders
● 2025-10-12 14:42:55 [200] POST /webhooks/shopify/orders (34ms)

Pass --output compact (or --output quiet for warnings and errors only) explicitly if you want the same behaviour on a machine that does have a terminal.

Event Gateway

The hookdeck gateway command provides full access to Hookdeck Event Gateway resources. Use these subcommands to manage infrastructure and inspect events:

Command groupDescription
hookdeck gateway connectionCreate and manage connections between sources and destinations
hookdeck gateway sourceManage inbound webhook sources
hookdeck gateway destinationManage destinations (HTTP endpoints, CLI, etc.)
hookdeck gateway eventList, get, retry, cancel, or mute events (processed deliveries)
hookdeck gateway requestList, get, and retry requests (raw inbound webhooks)
hookdeck gateway attemptList and get delivery attempts
hookdeck gateway transformationCreate and manage JavaScript transformations

Examples:

# List sources and destinations
hookdeck gateway source list
hookdeck gateway destination list

# List events (processed deliveries) and requests (raw inbound webhooks)
hookdeck gateway event list --status FAILED
hookdeck gateway request list --source-id src_abc123

# List attempts for an event
hookdeck gateway attempt list --event-id evt_abc123

# Create a transformation and test-run it
hookdeck gateway transformation create --name my-transform --code "addHandler(\"transform\", (request, context) => { return request; });"
hookdeck gateway transformation run --code "addHandler(\"transform\", (request, context) => { return request; });" --request '{"headers":{}}'

For complete command and flag reference, see REFERENCE.md.

Event Gateway MCP

The CLI includes an MCP (Model Context Protocol) server for investigating event traffic in production. It exposes read-only tools that let AI agents query your Hookdeck Event Gateway — inspect connections, trace requests through events and delivery attempts, review issues, and pull aggregate metrics.

Configure your MCP client (Cursor, Claude Desktop, or any MCP-compatible host):

Cursor (~/.cursor/mcp.json):

{
  "mcpServers": {
    "hookdeck": {
      "command": "hookdeck",
      "args": ["gateway", "mcp"]
    }
  }
}

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "hookdeck": {
      "command": "hookdeck",
      "args": ["gateway", "mcp"]
    }
  }
}

The client starts hookdeck gateway mcp as a stdio subprocess. If you haven't authenticated yet, the hookdeck_login tool is available to log in via the browser.

Read-only by default

The server starts read-only. Tools advertise only the actions that read data, so an agent is never offered an action it cannot perform. Pass --allow-write (or set HOOKDECK_MCP_ALLOW_WRITE=true) to enable creating, changing and deleting:

{
  "mcpServers": {
    "hookdeck": {
      "command": "hookdeck",
      "args": ["gateway", "mcp", "--allow-write"]
    }
  }
}

--read-only is accepted explicitly and wins if both are passed.

Pausing and unpausing a connection are available in both modes. Read-only is the mode incidents get investigated in, and stopping a misbehaving connection is the natural end of an investigation; both are reversible, and pausing buffers delivery rather than dropping events.

Available tools

Product tools are prefixed gateway_. Signing in and switching project are Hookdeck operations rather than Event Gateway ones, so they keep the platform hookdeck_ prefix and are shared with hookdeck outpost mcp.

Read tool (both modes)ActionsWrite tool (--allow-write only)Actions
hookdeck_projects_readlist——
hookdeck_projects_useuse——
hookdeck_login(sign in)——
gateway_connections_readlist, getgateway_connections_writecreate, upsert, update, delete, enable, disable
gateway_connections_pausepause, unpause——
gateway_sources_readlist, getgateway_sources_writecreate, upsert, update, delete, enable, disable
gateway_destinations_readlist, getgateway_destinations_writecreate, upsert, update, delete, enable, disable
gateway_transformations_readlist, get, rungateway_transformations_writecreate, upsert, update, delete
gateway_requests_readlist——
gateway_request_readget, raw_bodygateway_request_writeretry
gateway_events_readlist, list_ignored——
gateway_event_readget, raw_bodygateway_event_writeretry, cancel, mute
gateway_attempts_readlist, get——
gateway_issues_readlist, getgateway_issues_writeupdate, dismiss
gateway_metrics_readevents, requests, attempts, transformations——
gateway_bulk_readlist, get, plangateway_bulk_writecreate, cancel
gateway_helpoverview, per-tool topics——

Every tool name ends in _read or _write, so a client that grants permission per tool name can allow all reads with one rule — mcp__hookdeck-gateway__*_read — and be prompted on everything that changes data. A read tool is identical in both modes, so a grant written against one keeps meaning the same thing after the server is restarted with --allow-write.

Two tools match neither suffix, and both are deliberate. gateway_connections_pause changes delivery but stays available without --allow-write, because pausing a misbehaving connection is usually how an investigation ends. hookdeck_projects_use changes which project every later call targets, and gating it would strand a read-only session in whichever project it started in.

gateway_bulk_read's plan action estimates how many records a bulk operation would touch without running it — so the blast radius of a bulk retry or replay can be sized with no write access at all.

API key management is deliberately not exposed to MCP in any form. A key is a credential, and an agent able to mint one could grant itself access this server would otherwise refuse. Use the Hookdeck dashboard.

transformations run executes code without storing anything, but it is gated as a write: a read-only session should not be able to run caller-supplied code.

Events and requests are each split into a plural tool that searches and a singular tool that acts on one record:

  • gateway_events_read / gateway_requests_read (plural) take the filters and return IDs. They cannot fetch or change a single record.
  • gateway_event_read / gateway_request_read (singular) take an id and nothing else (plus connection_ids on request retry). They cannot search.

The usual flow is plural to find an ID, then singular with that ID. The split keeps ~20 list filters out of the schema for actions that only need an id.

gateway_events_read and gateway_requests_read list actions support the same filters as hookdeck gateway event list and hookdeck gateway request list — including payload search (body, headers, parsed_query, path) and date windows via *_after / *_before (ISO 8601; maps to API field[gte] / field[lte]). See gateway_help with topic gateway_events_read or gateway_requests_read for the full parameter list.

The only relationship traversal the API supports is request → events, and gateway_events_read owns both directions of it. Pass request_id with action list for the events a request produced, or action list_ignored for the ones a connection filter dropped. GET /events declares no request_id filter, so the tool queries the request's own events route instead — it takes the same filters, so every argument still applies (list_ignored's route is the exception: it takes id, paging and ordering only). There is no event_id filter on requests. To go the other way, read request_id off an event and call gateway_request_read with action get.

gateway_help reports which mode the session is in and lists only the actions it can perform.

Example prompts

Once the MCP server is configured, you can ask your agent questions like:

"Are any of my events failing right now?"
→ Agent uses gateway_issues to list open issues, then gateway_events to inspect recent failures.

"Show me the last 10 events for my Stripe source and check if any failed."
→ Agent uses gateway_sources to find the Stripe source, then gateway_events filtered by source and status.

"What's the error rate for my API destination over the last 24 hours?"
→ Agent uses gateway_metrics with measures like failed_count and count, grouped by destination.

"Trace request req_abc123 — what events did it produce, and did they all deliver successfully?"
→ Agent uses gateway_request to get the request, then its events action to list generated events.

"Why is my checkout endpoint returning 500s? Show me the latest attempt details."
→ Agent uses gateway_events filtered by status FAILED, then gateway_attempts to inspect delivery details.

"Pause the connection between Stripe and my staging endpoint while I debug."
→ Agent uses gateway_connections_read to find it, then gateway_connections_pause to stop delivery.

"Compare failure rates across all my destinations this week."
→ Agent uses gateway_metrics with dimensions set to destination_id and measures like error_rate.

"Find Stripe charge.succeeded events from the last week."
→ Agent uses gateway_events list with body filter {"type":"charge.succeeded"} and created_after / created_before ISO datetimes.

"Show failed events that had delivery attempts in the last 24 hours."
→ Agent uses gateway_events list with status FAILED and last_attempt_after set to yesterday's ISO datetime.

Outpost

Manage Hookdeck Outpost — your users (tenants), the destinations they own, and the events delivered to them.

These commands require an Outpost project. Switch with hookdeck project use; pointing them at an Event Gateway project reports which type the project is rather than failing obscurely.

hookdeck outpost [command]

# Available commands
hookdeck outpost tenant            # Manage tenants
hookdeck outpost destination       # Manage a tenant's destinations
hookdeck outpost destination-type  # Inspect available destination types and their fields
hookdeck outpost event             # Inspect published events, and retry delivery
hookdeck outpost attempt           # Inspect delivery attempts
hookdeck outpost publish           # Publish an event
hookdeck outpost topic             # Inspect available topics
hookdeck outpost metrics           # Query aggregate metrics
hookdeck outpost config            # Manage project configuration and the portal domain
hookdeck outpost status            # Show the deployment status

Destination config

Config and credential fields differ per destination type, and are defined by the Outpost deployment rather than the CLI, so they are passed as repeatable key=value pairs:

hookdeck outpost tenant upsert acme

hookdeck outpost destination create --tenant-id acme --type webhook \
  --config url=https://example.com/hooks --topics user.created

To find out what a type accepts, either ask for it directly or add --type to --help:

hookdeck outpost destination-type get kafka
hookdeck outpost destination create --type kafka --help

Both list every field with whether it is required, whether it is sensitive, and any values or format it is constrained to. --config-file accepts a JSON object, and nested values — should a type ever need them — use dotted paths (--config a.b=c).

Tenants and destinations also carry --metadata key=value (repeatable, or --metadata-file for a JSON object) for your own correlation data. It is replaced wholesale rather than merged, so pass every key you want to keep.

Publishing

hookdeck outpost publish is the one command that does not use the credentials stored by hookdeck login. The publish API requires a Hookdeck Project API key, so pass --api-key or set HOOKDECK_API_KEY:

hookdeck outpost publish --tenant-id acme --topic user.created \
  --data '{"user_id":"123"}' --api-key $HOOKDECK_API_KEY

Create a Project API key in the Hookdeck dashboard under your project's settings. See CLI authentication keys for how the key types differ.

Publishing is asynchronous: a successful response means the event was accepted, not delivered. Use hookdeck outpost attempt list to see the outcome.

For complete command and flag reference, see REFERENCE.md.

Outpost MCP

hookdeck outpost mcp starts an MCP server exposing your Outpost project to AI agents: tenants, their destinations, the events published to them, and every delivery attempt. Tools are prefixed outpost_, so this server and Event Gateway MCP can be configured in the same client.

{
  "mcpServers": {
    "hookdeck-outpost": {
      "command": "hookdeck",
      "args": ["outpost", "mcp"]
    }
  }
}

The client starts hookdeck outpost mcp as a stdio subprocess. If you haven't authenticated yet, the hookdeck_login tool logs in via the browser. The active project must be an Outpost project; hookdeck_projects_read lists the Outpost projects available to you and switches between them. Signing in and switching projects are Hookdeck operations rather than Outpost ones, so they keep the hookdeck_ prefix in both servers.

Read-only by default

The server starts read-only. Each tool advertises only the actions that read data, so an agent is never offered an action it cannot perform. Add --allow-write (or set HOOKDECK_MCP_ALLOW_WRITE=true; the flag wins) to enable the rest:

"args": ["outpost", "mcp", "--allow-write"]

--read-only is accepted as an explicit way to ask for the default, and wins if both are passed.

Two actions that only read are gated with the writes, because both return a reusable credential: outpost_tenants_write token mints a tenant-scoped access token, and outpost_tenants_read portal returns a URL granting access to a tenant's portal.

Publishing needs a Hookdeck Project API key, which the credentials stored by hookdeck login cannot substitute for. Without one the outpost_publish_write tool is not registered at all; pass --publish-api-key or set HOOKDECK_OUTPOST_PUBLISH_API_KEY to enable it.

HOOKDECK_API_KEY is deliberately not read here. Elsewhere in the CLI it means "a key to exchange for CLI credentials" and is commonly exported for CI, so reading it here would let an ambient variable silently grant an agent the ability to publish real events to real destinations.

Available tools

ToolDescription
hookdeck_loginSign in via the browser
hookdeck_projects_read / _useList the projects this credential can see, and switch the active one
outpost_tenants_read / _writeInspect tenants (list, get); manage them (upsert, delete, token, portal)
outpost_destinations_read / _writeInspect a tenant's destinations (list, get); manage them (create, update, delete, enable, disable)
outpost_events_read / _writeQuery published events (list, get); retry delivery
outpost_attempts_readQuery delivery attempts — status, response codes, retry history
outpost_publish_writePublish an event to a topic
outpost_topics_readList the topics available in the project
outpost_destination_types_readInspect destination types and the config and credential fields each accepts
outpost_metrics_readQuery aggregate publish and delivery metrics
outpost_config_read / _writeRead and change project configuration, including the portal's custom domain
outpost_status_readShow the deployment status
outpost_helpDiscover the available tools, their actions, and the current mode

Call outpost_help at any time to see which mode the session is in and which actions it can perform.

Manage connections

Create and manage webhook connections between sources and destinations with inline resource creation, authentication, processing rules, and lifecycle management. Use hookdeck gateway connection (or the backward-compatible alias hookdeck connection). For detailed examples with authentication, filters, retry rules, and rate limiting, see the complete connection management section below.

hookdeck gateway connection [command]

# Available commands
hookdeck gateway connection list      # List all connections
hookdeck gateway connection get       # Get connection details
hookdeck gateway connection create    # Create a new connection
hookdeck gateway connection upsert    # Create or update a connection (idempotent)
hookdeck gateway connection update    # Update a connection
hookdeck gateway connection delete    # Delete a connection
hookdeck gateway connection enable    # Enable a connection
hookdeck gateway connection disable   # Disable a connection
hookdeck gateway connection pause     # Pause a connection
hookdeck gateway connection unpause   # Unpause a connection

Sources and destinations

You can manage sources and destinations independently, not only inline when creating connections. Create reusable sources (e.g. Stripe, GitHub) and destinations (HTTP endpoints) that multiple connections can reference.

# List and inspect sources and destinations
hookdeck gateway source list
hookdeck gateway source get src_abc123

hookdeck gateway destination list
hookdeck gateway destination get dst_abc123

# Create a standalone destination
hookdeck gateway destination create --name "my-api" --type HTTP --url "https://api.example.com/webhooks"

See Sources and Destinations in REFERENCE.md.

Transformations

Transformations are JavaScript modules that modify requests before delivery. They are attached to connections and can add headers, transform the body, or filter events. Create, test, and manage transformations with hookdeck gateway transformation:

# Create a transformation
hookdeck gateway transformation create --name my-transform --code "addHandler(\"transform\", (request, context) => { return request; });"

# Test run transformation code (see transformed output)
hookdeck gateway transformation run --code "addHandler(\"transform\", (request, context) => { return request; });" --request '{"headers":{}}'

# List and use with connections (--transformation-name when creating connections)
hookdeck gateway transformation list

See Transformations in REFERENCE.md.

Requests, events, and attempts

Webhooks flow through Hookdeck as requests (raw inbound), then events (processed, routed), then attempts (delivery tries). Use these commands to inspect, filter, and retry:

# List requests (raw inbound webhooks) and filter by source
hookdeck gateway request list --source-id src_abc123
hookdeck gateway request get req_abc123

# List events (processed deliveries) by status
hookdeck gateway event list --status FAILED
hookdeck gateway event list --status PENDING
hookdeck gateway event get evt_abc123

# Retry a failed event or request
hookdeck gateway event retry evt_abc123
hookdeck gateway request retry req_abc123

# List attempts (individual delivery tries) for an event
hookdeck gateway attempt list --event-id evt_abc123

See Requests, Events, and Attempts in REFERENCE.md.

Manage active project

If you are a part of multiple projects, you can switch between them using our project management commands.

List projects

# List all projects
$ hookdeck project list
My Org / My Project (current)
My Org / Another Project
Another Org / Yet Another One

# Filter by organization and project name
$ hookdeck project list Org Proj
My Org / My Project (current)
My Org / Another Project

Select active project

hookdeck project use [<organization_name> [<project_name>]] [--local]

Flags:
  --local    Save project to current directory (.hookdeck/config.toml)

Project Selection Modes:

  • No arguments: Interactive prompt to select organization and project
  • One argument: Filter by organization name (prompts if multiple projects)
  • Two arguments: Directly select organization and project
$ hookdeck project use my-org my-project
Successfully set active project to: my-org / my-project

Configuration scope: Global vs Local

By default, project use saves your selection to the global configuration (~/.config/hookdeck/config.toml). You can pin a specific project to the current directory using the --local flag.

Configuration file precedence (only ONE is used):

The CLI uses exactly one configuration file based on this precedence:

  1. Custom config (via --hookdeck-config flag) - highest priority
  2. Local config - ${PWD}/.hookdeck/config.toml (if exists)
  3. Global config - ~/.config/hookdeck/config.toml (default)

Unlike Git, Hookdeck does not merge multiple config files - only the highest precedence config is used.

Examples:

# No local config exists → saves to global
$ hookdeck project use my-org my-project
Successfully set active project to: my-org / my-project
Saved to: ~/.config/hookdeck/config.toml

# Local config exists → automatically updates local
$ cd ~/repo-with-local-config  # has .hookdeck/config.toml
$ hookdeck project use another-org another-project
Successfully set active project to: another-org / another-project
Updated: .hookdeck/config.toml

# Create new local config
$ cd ~/my-new-repo  # no .hookdeck/ directory
$ hookdeck project use my-org my-project --local
Successfully set active project to: my-org / my-project
Created: .hookdeck/config.toml
⚠️  Security: Add .hookdeck/ to .gitignore (contains credentials)

# Update existing local config with confirmation
$ hookdeck project use another-org another-project --local
Local configuration already exists at: .hookdeck/config.toml
? Overwrite with new project configuration? (y/N) y
Successfully set active project to: another-org / another-project
Updated: .hookdeck/config.toml

Smart default behavior:

When you run project use with neither --local nor --hookdeck-config:

  • If .hookdeck/config.toml exists: Updates the local config
  • Otherwise: Updates the global config

This ensures your directory-specific configuration is preserved when it exists.

An explicit flag always beats this discovery. With --hookdeck-config <path>, that file is the one read and the one written, even when the working directory contains .hookdeck/config.toml:

$ cd ~/repo-with-local-config  # has .hookdeck/config.toml
$ hookdeck --hookdeck-config ~/ci.toml project use my-org my-project
Successfully set active project to: my-org / my-project
Saved to: ~/ci.toml            # the local config is untouched

Flag validation:

# ✅ Valid
hookdeck project use my-org my-project
hookdeck project use my-org my-project --local

# ❌ Invalid (cannot combine --hookdeck-config with --local)
hookdeck --hookdeck-config custom.toml project use my-org my-project --local
Error: --local and --hookdeck-config flags cannot be used together
  --local creates config at: .hookdeck/config.toml
  --hookdeck-config uses custom path: custom.toml

Benefits of local project pinning

  • Per-repository configuration: Each repository can use a different Hookdeck project
  • Team collaboration: Commit .hookdeck/config.toml to private repos (see security note)
  • No context switching: Automatically uses the right project when you cd into a directory
  • CI/CD friendly: Works seamlessly in automated environments

Security: Config files and source control

⚠️ IMPORTANT: Configuration files contain your Hookdeck credentials and should be treated as sensitive.

Config files store a CLI client key as api_key after hookdeck login, hookdeck login --cli-key, or hookdeck ci.

Recommended practices:

  • Private repositories: You MAY commit .hookdeck/config.toml if your repository is guaranteed to remain private and all collaborators should have access to the credentials.

  • Public repositories: You MUST add .hookdeck/ to your .gitignore:

    # Hookdeck CLI configuration (contains credentials)
    .hookdeck/
    
  • CI/CD environments: Use a Project API key via HOOKDECK_API_KEY (see Running in CI). hookdeck ci is optional — listen reads the variable itself:

    export HOOKDECK_API_KEY="your-project-api-key"
    hookdeck listen 3000
    

    To keep credentials out of the shared global config entirely, pass --hookdeck-config <path>, or use hookdeck ci --local to write only .hookdeck/config.toml in the working directory.

Checking which config is active:

$ hookdeck whoami
Logged in as: user@example.com
Active project: my-org / my-project
Config file: /Users/username/my-repo/.hookdeck/config.toml (local)

Removing local configuration:

To stop using local configuration and switch back to global:

$ rm -rf .hookdeck/
# Now CLI uses global config

Manage connections

Connections link sources to destinations and define how events are processed. You can create connections, including source/destination definitions, configure authentication, add processing rules (retry, filter, transform, delay, deduplicate), and manage their lifecycle.

Create a connection

Create a new connection between a source and destination. You can create the source and destination inline or reference existing resources:

# Basic connection with inline source and destination
$ hookdeck gateway connection create \
  --source-name "github-repo" \
  --source-type GITHUB \
  --destination-name "ci-system" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/webhooks"

✔ Connection created successfully
Connection: github-repo-to-ci-system (conn_abc123)
Source: github-repo (src_xyz789)
Source URL: https://hkdk.events/src_xyz789
Destination: ci-system (dst_def456)

# Using existing source and destination
$ hookdeck gateway connection create \
  --source-id src_existing123 \
  --destination-id dst_existing456 \
  --name "new-connection" \
  --description "Connects existing resources"

Add source authentication

Verify webhooks from providers like Stripe, GitHub, or Shopify by adding source authentication:

# Stripe webhook signature verification
$ hookdeck gateway connection create \
  --source-name "stripe-prod" \
  --source-type STRIPE \
  --source-webhook-secret "whsec_abc123xyz" \
  --destination-name "payment-api" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/webhooks/stripe"

# GitHub webhook signature verification
$ hookdeck gateway connection create \
  --source-name "github-webhooks" \
  --source-type GITHUB \
  --source-webhook-secret "ghp_secret123" \
  --destination-name "ci-system" \
  --destination-type HTTP \
  --destination-url "https://ci.example.com/webhook"

Add destination authentication

Secure your destination endpoint with bearer tokens, API keys, or basic authentication:

# Destination with bearer token
$ hookdeck gateway connection create \
  --source-name "webhook-source" \
  --source-type HTTP \
  --destination-name "secure-api" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/webhooks" \
  --destination-bearer-token "bearer_token_xyz"

# Destination with API key
$ hookdeck gateway connection create \
  --source-name "webhook-source" \
  --source-type HTTP \
  --destination-name "api-endpoint" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/webhooks" \
  --destination-api-key "your_api_key"

# Destination with custom headers
$ hookdeck gateway connection create \
  --source-name "webhook-source" \
  --source-type HTTP \
  --destination-name "custom-api" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/webhooks"

Configure retry rules

Add automatic retry logic with exponential or linear backoff:

# Exponential backoff retry strategy
$ hookdeck gateway connection create \
  --source-name "payment-webhooks" \
  --source-type STRIPE \
  --destination-name "payment-api" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/payments" \
  --rule-retry-strategy exponential \
  --rule-retry-count 5 \
  --rule-retry-interval 60000

Add event filters

Filter events based on request body, headers, path, or query parameters:

# Filter by event type in body
$ hookdeck gateway connection create \
  --source-name "events" \
  --source-type HTTP \
  --destination-name "processor" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/process" \
  --rule-filter-body '{"event_type":"payment.succeeded"}'

# Combined filtering
$ hookdeck gateway connection create \
  --source-name "shopify-webhooks" \
  --source-type SHOPIFY \
  --destination-name "order-processor" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/orders" \
  --rule-filter-body '{"type":"order"}' \
  --rule-retry-strategy exponential \
  --rule-retry-count 3

Configure rate limiting

Control the rate of event delivery to your destination:

# Limit to 100 requests per minute
$ hookdeck gateway connection create \
  --source-name "high-volume-source" \
  --source-type HTTP \
  --destination-name "rate-limited-api" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/endpoint" \
  --destination-rate-limit 100 \
  --destination-rate-limit-period minute

Configure delivery groups

Isolate delivery queues by a payload field and optionally give selected groups a different maximum rate:

$ hookdeck gateway connection create \
  --name "tenant-aware-delivery" \
  --source-name "events" \
  --source-type HTTP \
  --destination-name "tenant-aware-api" \
  --destination-type HTTP \
  --destination-url "https://api.example.com/endpoint" \
  --destination-rate-limit 100 \
  --destination-rate-limit-period second \
  --destination-delivery-group-key body.customer_id \
  --destination-delivery-group-rate 5 \
  --destination-delivery-group-rate-period second \
  --destination-delivery-group-overrides '{"cus_priority":{"rate":50,"rate_period":"second"}}'

Use --config or --config-file when you need to set delivery_policy.groups directly, including setting groups to null to disable grouping.

Upsert connections

Create or update connections idempotently based on connection name - perfect for CI/CD and infrastructure-as-code workflows:

# Create if doesn't exist, update if it does
$ hookdeck gateway connection upsert my-connection \
  --source-name "stripe-prod" \
  --source-type STRIPE \
  --destination-name "api-prod" \
  --destination-type HTTP \
  --destination-url "https://api.example.com"

# Partial update of existing connection
$ hookdeck gateway connection upsert my-connection \
  --description "Updated description" \
  --rule-retry-count 5

# Preview changes without applying (dry-run)
$ hookdeck gateway connection upsert my-connection \
  --description "New description" \
  --dry-run

-- Dry Run: UPDATE --
Connection 'my-connection' (conn_123) will be updated with the following changes:
- Description: "New description"

List and filter connections

View all connections with flexible filtering options:

# List all connections
$ hookdeck gateway connection list

# Filter by source or destination
$ hookdeck gateway connection list --source-id src_abc123
$ hookdeck gateway connection list --destination-id dst_def456

# Filter by name pattern
$ hookdeck gateway connection list --name "production-*"

# Include disabled connections
$ hookdeck gateway connection list --disabled

# Output as JSON
$ hookdeck gateway connection list --output json

Get connection details

View detailed information about a specific connection:

# Get by ID
$ hookdeck gateway connection get conn_123abc

# Get by name
$ hookdeck gateway connection get "my-connection"

# Get as JSON
$ hookdeck gateway connection get conn_123abc --output json

# Include destination authentication credentials
$ hookdeck gateway connection get conn_123abc --include-destination-auth --output json

Connection lifecycle management

Control connection state and event processing behavior:

# Disable a connection (stops receiving events entirely)
$ hookdeck gateway connection disable conn_123abc

# Enable a disabled connection
$ hookdeck gateway connection enable conn_123abc

# Pause a connection (queues events without forwarding)
$ hookdeck gateway connection pause conn_123abc

# Resume a paused connection
$ hookdeck gateway connection unpause conn_123abc

State differences:

  • Disabled: Connection stops receiving events entirely
  • Paused: Connection queues events but doesn't forward them (useful during maintenance)

Delete a connection

Delete a connection permanently:

# Delete with confirmation prompt
$ hookdeck gateway connection delete conn_123abc

# Delete by name
$ hookdeck gateway connection delete "my-connection"

# Skip confirmation
$ hookdeck gateway connection delete conn_123abc --force

For complete flag documentation and all examples, see REFERENCE.md.

Telemetry

The Hookdeck CLI collects anonymous telemetry to help improve the tool. You can opt out at any time:

# Disable telemetry
hookdeck telemetry disabled

# Re-enable telemetry
hookdeck telemetry enabled

You can also disable telemetry by setting the HOOKDECK_CLI_TELEMETRY_DISABLED environment variable to 1 or true.

Configuration files

The Hookdeck CLI uses configuration files to store the your keys, project settings, profiles, and other configurations.

Configuration file name and locations

The CLI will look for the configuration file in the following order:

  1. The --hookdeck-config flag, which allows you to specify a custom configuration file path per command.
  2. The HOOKDECK_CONFIG_FILE environment variable (path to the config file).
  3. The local directory .hookdeck/config.toml.
  4. The default global configuration file location.

The file chosen this way is the file the CLI reads and writes. An explicit path beats discovery: pass --hookdeck-config <path> and no other configuration file is touched, whatever the working directory contains. --local (on login, ci and project use) instead pins both to ./.hookdeck/config.toml, and cannot be combined with --hookdeck-config.

Default configuration Location

The default configuration location varies by operating system:

  • macOS/Linux: ~/.config/hookdeck/config.toml
  • Windows: %USERPROFILE%\.config\hookdeck\config.toml

The CLI follows the XDG Base Directory Specification on Unix-like systems, respecting the XDG_CONFIG_HOME environment variable if set.

Configuration File Format

The Hookdeck CLI configuration file is stored in TOML format and typically includes:

api_key = "api_key_xxxxxxxxxxxxxxxxxxxx"
project_id = "tm_xxxxxxxxxxxxxxx"
project_type = "Gateway" | "Outpost" | "Console"

Local Configuration

The Hookdeck CLI also supports local configuration files. If you run the CLI commands in a directory that contains a .hookdeck/config.toml file, the CLI will use that file for configuration instead of the global one.

Using Profiles

The config.toml file supports profiles which give you the ability to save different CLI configuration within the same configuration file.

You can create new profiles by either running hookdeck login or hookdeck use with the -p flag and a profile name. For example:

hookdeck login -p dev

If you know the name of your Hookdeck organization and the project you want to use with a profile you can use the following:

hookdeck project use org_name proj_name -p prod

This will results in the following config file that has two profiles:

profile = "dev"

[dev]
  api_key = "api_key_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  project_id = "tm_5JxTelcYxOJy"
  project_type = "Gateway"

[prod]
  api_key = "api_key_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
  project_id = "tm_U9Zod13qtsHp"
  project_type = "Gateway"

This allows you to run commands against different projects. For example, to listen to the webhooks source in the dev profile, run:

hookdeck listen 3030 webhooks -p dev

To listen to the webhooks source in the prod profile, run:

hookdeck listen 3030 webhooks -p prod

Global Flags

The following flags can be used with any command:

  • --color: Turn on/off color output (on, off, auto).
  • --hookdeck-config: Path to the CLI configuration file. You can also set the HOOKDECK_CONFIG_FILE environment variable to the config file path.
  • --device-name: A unique name for your device.
  • --insecure: Allow invalid TLS certificates.
  • --log-level: Set the logging level (debug, info, warn, error).
  • --profile or -p: Use a specific configuration profile.

Authentication uses command-specific flags (hookdeck login --cli-key, hookdeck ci --api-key, or HOOKDECK_API_KEY), not global flags.

There are also hidden flags for development and debugging (not listed in user-facing help); for example --api-base sets the API base URL when testing against a local stack:

  • --api-base: Sets the API base URL.
  • --dashboard-base: Sets the web dashboard base URL.
  • --console-base: Sets the web console base URL.
  • --ws-base: Sets the Websocket base URL.

Troubleshooting

Homebrew: migrating from the third-party tap to homebrew-core

The stable hookdeck formula now lives in homebrew-core. The third-party hookdeck/hookdeck tap publishes only the beta formula.

If you installed via the tap, the move is automatic: brew update && brew upgrade will pull the next stable version from homebrew-core. No action needed.

If you want to switch immediately:

brew update
brew upgrade hookdeck

Homebrew: hookdeck and hookdeck-beta conflict on link

Both formulae install a binary called hookdeck, so only one can be linked at a time. Switching between them requires --overwrite:

# Switch from beta to stable
brew link --overwrite hookdeck

# Switch from stable to beta
brew link --overwrite hookdeck-beta

Homebrew: beta install asks for a brew trust step

Once HOMEBREW_REQUIRE_TAP_TRUST becomes the default in Homebrew 5.2.0 / 6.0.0, installing the beta from the third-party tap requires:

brew trust --formula hookdeck/hookdeck/hookdeck-beta

The stable formula lives in homebrew-core and is unaffected.

Developing

Running from source:

go run main.go

Generating REFERENCE.md

The REFERENCE.md file is generated from Cobra command metadata. After changing commands, flags, or help text, regenerate it in place:

go run ./tools/generate-reference

To validate that REFERENCE.md is up to date (useful in CI):

go run ./tools/generate-reference --check

Build from source by running:

go build

Then run the locally generated hookdeck-cli binary:

./hookdeck-cli

Testing the npm package build

To test the npm package build process locally (including the wrapper script), you can use the automated test script:

# Run the automated test script (recommended)
./test-scripts/test-npm-build.sh

The test script will:

  • Build all 6 platform binaries using GoReleaser
  • Verify the binaries directory structure
  • Test the wrapper script on your current platform
  • Verify npm pack includes all required files

Manual testing (if you prefer step-by-step):

# Install GoReleaser (if not already installed)
# Option 1: Using Homebrew (recommended on macOS)
brew install goreleaser

# Option 2: Download binary from GitHub releases
# Visit https://github.com/goreleaser/goreleaser/releases/latest

# Build all platform binaries for npm
goreleaser build -f .goreleaser/npm.yml --snapshot --clean

# Verify binaries directory structure
ls -R binaries/

# Test the wrapper script on your platform
node bin/hookdeck.js --version

# Test npm package creation (dry-run)
npm pack --dry-run

This will create the binaries/ directory with all 6 platform binaries, allowing you to test the wrapper script locally before publishing.

Testing

Running Acceptance Tests

The Hookdeck CLI includes comprehensive acceptance tests written in Go. These tests verify end-to-end functionality by executing the CLI and validating outputs.

Local testing:

# Run all acceptance tests
go test ./test/acceptance/... -v

# Run specific test
go test ./test/acceptance/... -v -run TestCLIBasics

# Skip acceptance tests (short mode)
go test ./test/acceptance/... -short

Environment setup:

For local testing, create a .env file in test/acceptance/:

# test/acceptance/.env
HOOKDECK_CLI_TESTING_API_KEY=your_api_key_here

CI/CD:

In CI environments, set the HOOKDECK_CLI_TESTING_API_KEY environment variable directly in your workflow configuration or repository secrets.

For detailed testing documentation and troubleshooting, see test/acceptance/README.md.

Testing npm package and wrapper script

The npm package includes a wrapper script (bin/hookdeck.js) that detects the platform and executes the correct binary.

Quick test (using automated script):

./test-scripts/test-npm-build.sh

Manual testing:

# Ensure GoReleaser is installed (see "Testing the npm package build" section above)

# Build all platform binaries
goreleaser build -f .goreleaser/npm.yml --snapshot --clean

# Test wrapper script on current platform
node bin/hookdeck.js version

# Verify wrapper script can find binary
node bin/hookdeck.js --help

# Test npm pack includes all files
npm pack --dry-run | grep -E "(bin/hookdeck.js|binaries/)"

Note: The wrapper script expects binaries in binaries/{platform}-{arch}/hookdeck[.exe]. When building locally, ensure all platforms are built or the wrapper will fail for missing platforms.

Testing against a local API

When testing against a non-production Hookdeck API, you can use the --api-base and --ws-base flags, e.g.:

./hookdeck-cli --api-base http://localhost:9000 --ws-base ws://localhost:3003 listen 1234

Also if running in Docker, the equivalent command would be:

docker run --rm -it \
    -v $HOME/.config/hookdeck:/root/.config/hookdeck hookdeck/hookdeck-cli \
    --api-base http://host.docker.internal:9000 \
    --ws-base ws://host.docker.internal:3003 \
    listen \
    http://host.docker.internal:1234

Testing the published npm package

To verify that the published npm package installs correctly and has the expected layout (wrapper script and platform binaries), use the local test script. It installs into a controlled directory (no global install) and runs the same checks as the test-npm-install CI workflow.

# Test with @latest (default)
./test-scripts/test-npm-install-local.sh

# Test with a specific version or tag
./test-scripts/test-npm-install-local.sh 1.7.1
./test-scripts/test-npm-install-local.sh @beta

Install output is written to test-scripts/.install-test/ (gitignored).

Releasing

This section describes the release process for the Hookdeck CLI.

Maintainers using AI assistants: see .agents/skills/hookdeck-cli-release/ for the release skill (automation details and release-note workflow).

Release Process

The release workflow supports tagging from ANY branch - it automatically detects which branch contains the tag. This means you can create beta releases directly from feature branches for testing before merging to main.

Stable Release (Preferred Method: GitHub UI)

  1. Ensure all tests pass on main
  2. Go to the GitHub Releases page
  3. Click "Draft a new release"
  4. Create a new tag with a stable version (e.g., v1.3.0)
  5. Target the main branch
  6. Generate release notes or write them manually
  7. Publish the release

The GitHub Actions workflow will automatically:

  • Build binaries for all platforms
  • Create a stable GitHub release
  • Publish to NPM with the latest tag
  • Update package managers:
    • Homebrew: stable formula in homebrew-core is auto-bumped by Homebrew's BrewTestBot (no action from us; runs every ~3 hours after the tag is published). The hookdeck-beta formula in our third-party tap is updated only on pre-release tags.
    • Scoop: hookdeck package
    • Docker: Updates both the version tag and latest

Do not push a bare git tag. Any v* tag starts the release pipeline, so a hand-pushed tag publishes to npm, Homebrew, Scoop and Docker from a release with no notes — and npm will not let you republish that version. Always create the release (UI or gh), which makes the tag for you. Never use a v* tag as a bookmark.

Alternative (Command Line):

gh release create v1.3.0 --target main --title "v1.3.0" --notes-file notes.md

Pre-release from Main (General Beta Testing)

For general beta testing of features that have been merged to main:

Preferred Method: GitHub UI

  1. Ensure main branch is in the desired state
  2. Go to the GitHub Releases page
  3. Click "Draft a new release"
  4. Create a new tag with pre-release version (e.g., v1.3.0-beta.1)
  5. Target the main branch
  6. Check "Set as a pre-release"
  7. Publish the release
  8. GitHub Actions will build and publish with npm tag beta

Alternative (Command Line):

gh release create v1.3.0-beta.1 --target main --prerelease \
  --title "v1.3.0-beta.1" --notes-file notes.md

Installing beta releases:

# NPM
npm install hookdeck-cli@beta -g

# Homebrew
brew install hookdeck/hookdeck/hookdeck-beta

# To force the symlink update and overwrite all conflicting files:
# brew link --overwrite hookdeck-beta

# Scoop
scoop install hookdeck-beta

# Docker
docker run hookdeck/hookdeck-cli:v1.3.0-beta.1 version

Pre-release from Feature Branch (Feature-Specific Testing)

For testing a specific feature in isolation before merging to main:

Preferred Method: GitHub UI

  1. Ensure your feature branch is pushed to origin
  2. Go to the GitHub Releases page
  3. Click "Draft a new release"
  4. Create a new tag with pre-release version (e.g., v1.3.0-beta.1)
  5. Target your feature branch (e.g., feat/my-feature)
  6. Check "Set as a pre-release"
  7. Add notes about what's being tested
  8. Publish the release
  9. GitHub Actions will automatically detect the branch and build from it

Alternative (Command Line):

gh release create v1.3.0-beta.1 --target feat/my-feature --prerelease \
  --title "v1.3.0-beta.1" --notes-file notes.md

Installing beta releases:

# NPM
npm install hookdeck-cli@beta -g

# Homebrew
brew install hookdeck/hookdeck/hookdeck-beta

# To force the symlink update and overwrite all conflicting files:
# brew link --overwrite hookdeck-beta

# Scoop
scoop install hookdeck-beta

# Docker
docker run hookdeck/hookdeck-cli:v1.3.0-beta.1 version

Note: Only stable releases (without pre-release identifiers like -beta, -alpha) will update the latest tags across all distribution channels.

Repository Setup

GitHub Repository Settings

To maintain code quality and protect the main branch, configure the following settings in your GitHub repository:

Default Branch:

  1. Go to Settings → Branches
  2. Set default branch to main (if not already set)

Branch Protection Rules for main:

  1. Go to Settings → Branches → Branch protection rules
  2. Add rule for main branch
  3. Enable the following settings:
    • Require a pull request before merging
      • Require approvals: 1 (or as needed for your team)
    • Require status checks to pass before merging
      • Require branches to be up to date before merging
      • Add status check: build-linux, build-mac, build-windows (from test workflow)
    • Do not allow bypassing the above settings
    • Restrict force pushes (recommended)
    • Restrict deletions (recommended)

These settings ensure that all changes to main go through proper review and testing before being merged.

CLI authentication keys

Reference for how Hookdeck credentials relate to CLI commands. After any successful login or hookdeck ci, the CLI stores a CLI client key in your config file as api_key (see Configuration files). The same field name is used regardless of how the key was obtained.

The api_key field in your config is not a Project API key. It holds whichever CLI client key the last login produced. The field name is historical, so you cannot tell from the config file alone which kind of credential you have, or what it is allowed to do.

Which key can do what

hookdeck login
hookdeck login --cli-key
hookdeck ci --api-keyProject API key
(dashboard)
What it isCLI client key, tied to your userCLI client key, tied to one projectLong-lived key from project settings
Stored in config as api_keyYesYesNo — exchanged, never stored
hookdeck listen, hookdeck gateway …YesYesNo
hookdeck project list / project useYesNo — single project, no userNo
Accepted by hookdeck ci --api-keyNoNoYes

The distinction that catches people out is the middle column: a key from hookdeck ci works fine for everyday commands but is pinned to one project, so anything that spans projects fails.

Check which key you have

hookdeck whoami shows the active project but not the key's scope. To tell the two CLI client keys apart, ask for something only a user-associated key can do:

hookdeck project list
  • A list of projects — you have a user-associated key and can switch projects.
  • An error saying the credential is scoped to a single project — you have a project-scoped key from hookdeck ci. Run hookdeck login (or hookdeck login --cli-key <key>) for account-wide access.

CLI client keys (what the CLI runs as)

A CLI client key identifies the Hookdeck CLI to the API (cli authentication). It powers hookdeck listen, hookdeck gateway …, and most other commands after you are configured.

How you get itTypical commandServer check
Browser or device loginhookdeck loginValidate, or poll until fully associated (see below)
Product UI copy-pastehookdeck login --cli-key <key>Validate (user and project set at creation)
CI / automationhookdeck ci --api-key …Creates a team-scoped CLI client key (see below)
Guest sandboxhookdeck listen (no prior login)Guest user and project set at creation

Each CLI client key on the server has optional user_id and team_id fields. There are three association states:

Stateuser_idteam_idWhen
Pending device loginnullnullStart of hookdeck login browser flow, before you finish sign-in
CI / automationnullsetAfter hookdeck ci (POST /cli-auth/ci)
Fully associatedsetsetDashboard/Console UI keys, guest sandboxes, or after device login completes
  • Pending device login — The CLI polls GET /cli-auth/poll until both user_id and team_id are set. GET /cli-auth/validate does not succeed until association is complete.
  • CI keys — Scoped to a project (team) but not tied to a user. Validate works immediately; poll requires both fields, so CI keys are configured via validate, not poll.
  • Fully associated — User and project are set. Validate works immediately. Keys from dashboard onboarding or Console CLI destination setup are fully associated when created.

Project API key (input to hookdeck ci only)

A Project API key is a long-lived key from the Hookdeck dashboard (project settings). It is not what the CLI stores in config for day-to-day use. Pass it once to:

hookdeck ci --api-key $HOOKDECK_API_KEY   # or set HOOKDECK_API_KEY

The CLI calls POST /cli-auth/ci with that Project API key; the server returns a CLI client key scoped to that project. That returned key is saved as api_key in your config. Use hookdeck listen and gateway commands after hookdeck ci, not the original Project API key.

Guest credentials

If you run hookdeck listen without an existing profile, the CLI can create a guest sandbox (POST /cli/guest). The config may include guest_url and an api_key for that sandbox. hookdeck login reuses that existing guest key and waits for the server to upgrade the guest account into a permanent account.

project list / project use

Listing and switching projects requires a user-associated CLI client key (for example from hookdeck login or hookdeck login --cli-key). The server returns all projects your user can access. Keys from hookdeck ci are scoped to a single project with no user association—they cannot list or switch projects across your account. A Project API key alone is not sufficient either; use interactive login or a product-issued CLI key instead.

License

Copyright (c) Hookdeck. All rights reserved.

Licensed under the Apache License 2.0 license.

Collected info

  • ★ 363 stars
  • ⎇ 22 forks
  • Language: Go
  • Source updated: 9/27/2026

Config for your environment

Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.

Tool

OS

Config file: ~/.cursor/mcp.json

{
  "mcpServers": {
    "mcp-server": {
      "url": "{MCP_ENDPOINT_URL}"
    }
  }
}

Paste into mcpServers in the config file. Restart Cursor after saving.

If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.