Managing plugins
View installed plugins
Go to Settings → Plugins. You’ll see all installed plugins grouped by type:- Built-in — ship with HQ and cannot be removed (e.g. Usage Alerts)
- Installed — local or webhook plugins you’ve added
Enable or disable a plugin
Toggle the switch next to any plugin. Disabled plugins stop receiving events immediately. Re-enabling resumes event delivery. No events are queued while disabled — they’re simply skipped.Remove a plugin
Click the⋯ menu on any non-builtin plugin and select Remove. This deletes the plugin and all its configuration. Event history is retained for 30 days.
Creating a webhook plugin
Webhook plugins are the fastest way to integrate HQ with external services. No gateway code needed.Prepare your endpoint
Deploy an HTTP endpoint that accepts POST requests with a JSON body. This can be:
- A Zapier/Make/n8n catch hook
- A Slack incoming webhook
- A custom service (Cloudflare Worker, AWS Lambda, Vercel Edge Function)
- Any URL that returns a 2xx status
Add the plugin
Go to Settings → Plugins → Add plugin. Fill in:
- Name — a display name (e.g. “Slack Notifications”)
- Description — optional, shown in the plugin list
- Webhook URL — your endpoint URL
- Signing secret — optional, used for HMAC-SHA256 signature verification
- Events — check the events you want to receive
Test it
Trigger one of the events you subscribed to (e.g. create a task if you selected “Task Created”). Check your endpoint for the incoming POST request.
Verify signatures (recommended)
If you set a signing secret, verify the
X-HQ-Signature header on incoming requests. See the verification examples in the concepts page.Webhook payload format
Every webhook POST contains the full event envelope as JSON:| Header | Value |
|---|---|
Content-Type | application/json |
X-HQ-Event | Event type (e.g. task.completed) |
X-HQ-Plugin-Id | Plugin identifier |
X-HQ-Delivery | Unique delivery ID |
X-HQ-Signature | sha256=<hex> (only if signing secret set) |
Creating a local plugin
Local plugins run on the gateway as Python modules. They have full access to the plugin SDK — state persistence, secret resolution, and Supabase queries.Edit manifest.json
Set the plugin identity and subscriptions:Key fields:
id— unique slug, must match the directory namehooks— array of event types to subscribe to (see event reference)config_schema— JSON Schema for operator-configurable settingscapabilities— what SDK features the plugin uses
Plugin SDK reference
PluginEvent
The event object passed toon_event():
| Field | Type | Description |
|---|---|---|
event_id | str | Unique event UUID |
event_type | str | Event name (e.g. task.completed) |
occurred_at | str | ISO 8601 timestamp |
tenant_id | str | Tenant UUID |
entity_type | str | None | Source table name |
entity_id | str | None | Entity UUID |
payload | dict | Event-specific data |
PluginResponse
Optional return value fromon_event():
| Field | Type | Description |
|---|---|---|
data | dict | None | Arbitrary response data (logged) |
log_message | str | None | Human-readable message for the event log |
StateClient
Scoped key-value store backed byhq_plugin_state:
SecretsClient
Read-only access to gateway secrets (decrypted.env files written by secrets_sync):
SupabaseClient
Read-only queries against HQ tables:Example plugins
Slack notifications (webhook)
Slack notifications (webhook)
Type: Webhook — no gateway code neededRegister a Slack incoming webhook URL as a webhook plugin. Subscribe to
task.completed, agent.status_changed, and budget.exceeded. Slack renders the raw JSON payload as a message.For richer formatting, deploy a small relay service that transforms HQ events into Slack Block Kit messages.Linear sync (local)
Linear sync (local)
Type: Local pluginCreate issues in Linear when HQ tasks are created, and close them when tasks complete. Uses the state client to track issue mappings:
PagerDuty alerts (webhook)
PagerDuty alerts (webhook)
Type: WebhookPoint a webhook plugin at PagerDuty’s Events API v2 endpoint. Subscribe to
budget.exceeded and agent.status_changed. When an agent goes over budget or enters an error state, PagerDuty creates an incident.Audit logging to S3 (local)
Audit logging to S3 (local)
Type: Local pluginSubscribe to all events. On each event, append a JSON line to a local file. Periodically upload the file to S3 using a state-tracked cursor. Useful for compliance or long-term retention beyond the 30-day event log.
Contributing a plugin
We welcome plugin contributions. The goal is a rich ecosystem of community-built integrations.Implement and test
Follow the local plugin steps above. Test by triggering events and checking your handler’s behavior.
Document
Add a clear description in
manifest.json. If your plugin has non-obvious configuration, add comments in the config schema.Open a PR
Submit your plugin directory (
gateway/plugins/your-plugin/) as a PR against main. Include:- What the plugin does and why it’s useful
- Any external service dependencies
- Example config values
gateway/plugins/CONTRIBUTING.md in the repository.
Troubleshooting
Plugin isn’t receiving events:- Check that the plugin is enabled (toggle should be on in Settings → Plugins)
- Verify the plugin subscribes to the correct hooks in its manifest or configuration
- Check the plugin runner logs:
docker compose logs -f runner - For webhook plugins: confirm the URL is reachable from the gateway host
- Click the plugin in Settings → Plugins and check Recent activity for error messages
- Verify the webhook URL returns a 2xx status code
- Check that your endpoint handles the JSON payload format correctly
- If using signatures: ensure the signing secret matches on both sides
- Confirm
handler.pyexists in the plugin directory and exports aHandlerclass - Check that
HandlersubclassesBasePluginfromgateway.plugins.sdk - Look for import errors in the plugin runner logs
- Verify the
entry_moduleinhq_pluginsmatches the directory name
- Check that your plugin declares
state.writein capabilities - Verify the scope parameters match between
set()andget()calls - Check the
hq_plugin_statetable directly for your plugin’s entries

