Plugins let you react to HQ events and take action — send notifications, sync data to external tools, enforce policies, or run custom logic. This guide walks through creating both types of plugins and managing them from the UI. Where to go: Settings → PluginsDocumentation Index
Fetch the complete documentation index at: https://docs.yourhq.ai/llms.txt
Use this file to discover all available pages before exploring further.
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

