Skip to content

Hooks API

Base path: /api/hooks

All endpoints require authentication. See API Overview for auth details.

GET /api/hooks

Response:

{
"hooks": [
{
"id": "hook_abc123",
"name": "Slack task alerts",
"event_type": "task.failed",
"hook_type": "http",
"enabled": true,
"scope": "global",
"config": {"url": "https://hooks.slack.com/...", "method": "POST"},
"filter_expr": null,
"priority": 0,
"tags": [],
"created_at": "2026-01-01T00:00:00Z"
}
]
}
POST /api/hooks

HTTP hook (outbound webhook):

{
"name": "Notify on task failure",
"event_type": "task.failed",
"hook_type": "http",
"config": {
"url": "https://your-server.com/webhook",
"secret": "your-hmac-secret-min-32-chars-long",
"headers": {"X-Source": "snippbot"},
"queued": true,
"retry_delay_s": 10,
"retry_multiplier": 2.0,
"max_delay_s": 3600
},
"max_retries": 5,
"filter_expr": {"field": "data.agent_id", "op": "eq", "value": "agent_123"}
}

Python hook (execute Python code):

{
"name": "Log task completion",
"event_type": "task.completed",
"hook_type": "python",
"source_code": "print(event['data']['task_id'])"
}

Builtin hook (built-in action):

{
"name": "Announce on Slack",
"event_type": "project.updated",
"hook_type": "builtin",
"config": {
"channel": "slack:#general",
"template": "Project {{data.name}} moved to {{data.status}}"
}
}

Returns 201 with the created hook object directly.

GET /api/hooks/{id}
PUT /api/hooks/{id}

Send only fields to update:

{"enabled": false}

To toggle enabled state only, use the dedicated endpoint:

PATCH /api/hooks/{id}/toggle
DELETE /api/hooks/{id}

Returns {"success": true}.

POST /api/hooks/{id}/test

Fires the hook with a synthetic test event. Returns the execution record:

{
"id": "exec_abc123",
"hook_id": "hook_abc123",
"event_type": "task.failed",
"started_at": "2026-03-02T14:00:00Z",
"completed_at": "2026-03-02T14:00:00.045Z",
"duration_ms": 45,
"status": "success"
}
GET /api/hooks/bundled

Returns built-in hooks that ship with Snippbot.

GET /api/hooks/event-types

Returns all hookable event types grouped by category:

{
"event_types": {
"task": ["task.queued", "task.started", "task.completed", "task.failed", "task.retrying", "task.skipped"],
"project": ["project.created", "project.updated", "project.deleted", "project.completed"],
"memory": ["memory.stored", "memory.consolidated", "memory.recalled"],
"channel": ["channel.message.received", "channel.message.sent", "channel.message.failed"],
"scheduler": ["scheduler.started", "scheduler.stopped", "scheduler.tick"]
}
}

Common events:

EventFired when
task.completedA task finishes successfully
task.failedA task fails

| project.created | A project is created | | project.updated | Project status changes | | memory.stored | Memory is persisted | | channel.message.received | A message arrives via a channel |

GET /api/hooks/{id}/executions

Query: status (success, error, running, timeout, skipped), since, limit, offset

Response:

{
"executions": [
{
"id": "exec_abc123",
"hook_id": "hook_abc123",
"event_type": "task.failed",
"status": "success",
"started_at": "2026-03-02T14:00:00Z",
"completed_at": "2026-03-02T14:00:00.048Z",
"duration_ms": 48
}
]
}
GET /api/hooks/{id}/analytics

Query: period (hourly, daily), start, end

Returns execution analytics for a specific hook.

Register inbound webhook endpoints to receive external events. These live under /api/webhooks (separate from /api/hooks).

GET /api/webhooks
POST /api/webhooks
{
"name": "GitHub push events",
"secret": "github-webhook-secret"
}

Returns:

{
"id": "wh_abc123",
"url": "https://your-snippbot/api/webhooks/{webhook_id}/receive",
"secret": "github-webhook-secret"
}
GET /api/webhooks/{id}
PUT /api/webhooks/{id}
DELETE /api/webhooks/{id}
GET /api/webhooks/{id}/deliveries
POST /api/webhooks/{webhook_id}/receive

This is the URL external services send events to. It is authenticated via HMAC signature, not bearer token.

POST /api/webhooks/{id}/deliveries/{delivery_id}/replay

HTTP hooks with queue-backed delivery enabled track each delivery attempt. These endpoints manage the delivery queue.

GET /api/hooks/{id}/deliveries

Query: status (pending, processing, success, failed, dead_letter), limit, offset

Response:

{
"deliveries": [
{
"id": "del_abc123",
"hook_id": "hook_abc123",
"event_type": "task.completed",
"status": "success",
"attempt_count": 1,
"response_status": 200,
"duration_ms": 450,
"created_at": "2026-04-17T00:00:00Z"
}
],
"stats": {"success": 5, "failed": 0, "pending": 0}
}
POST /api/hooks/{id}/deliveries/{delivery_id}/retry

Only failed or dead_letter deliveries can be retried. Returns 409 otherwise.

POST /api/hooks/{id}/test-delivery

Sends a synthetic test.notification event to the hook’s URL. Returns 202 with the delivery object.

GET /api/hooks/deliveries/queue-stats

Returns global delivery counts across all HTTP hooks:

{
"stats": {
"pending": 0,
"processing": 0,
"success": 42,
"failed": 1,
"dead_letter": 0
}
}
GET /api/hooks/analytics/global

Returns execution statistics across all hooks:

{
"total_deliveries": 1247,
"success_rate": 0.98,
"avg_latency_ms": 52,
"by_event": {
"task.completed": {"deliveries": 800, "success_rate": 0.99},
"task.failed": {"deliveries": 447, "success_rate": 0.97}
},
"dead_letter_count": 3
}

Inline hooks retry immediately with fast exponential backoff:

AttemptDelay
1100ms
2200ms
3400ms
N100ms × 2^(N-1)

Queue-backed HTTP hooks (config.queued: true)

Section titled “Queue-backed HTTP hooks (config.queued: true)”

Queued hooks use configurable exponential backoff with jitter:

ConfigDefaultDescription
retry_delay_s10Initial retry delay in seconds
retry_multiplier2.0Backoff multiplier
max_delay_s3600Maximum delay cap
max_retries5Retries before dead letter

After max_retries failures, deliveries move to the dead letter queue and can be manually retried via the API or UI.

Both inbound webhooks and outbound HTTP hooks include a signature header when a secret is configured:

X-Snippbot-Signature: sha256=<hex_digest>

Verify in Python:

import hmac, hashlib
def verify(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)