The gateway now ships in the
praisonai-bot package. praisonai serve gateway still works exactly as documented here; for a standalone install see praisonai-bot Migration.POST /hooks/<path> endpoint on the gateway — point any external service (GitHub Actions, Linear, Gmail push, Sentry alerts) at it and the gateway runs an agent and delivers the reply to a configured channel.
/hooks/<path>; the gateway authenticates the payload, runs the mapped agent, and delivers the reply to the configured channel.
Quick Start
How It Works
Three Ways to Register a Hook
- Python
- YAML (gateway.yaml)
- CLI
HookConfig Options
| Option | Type | Default | Description |
|---|---|---|---|
path | str | required | URL segment after /hooks/. "gmail" exposes POST /hooks/gmail. Must be non-empty. |
agent | str | None | Agent name/id to run. Falls back to the gateway’s first registered agent. |
action | str | "agent" | "agent" runs a new turn; "wake" nudges an existing session without a new message. |
auth | str | None | Bearer token required on inbound requests. Falls back to the gateway’s global auth_token. |
session_key | str | None | Template for the session id, e.g. "hook:gmail:{message_id}". Defaults to "hook:{path}". |
idempotency_key | str | None | Template for the dedup key. When unset, hashes the entire payload. |
deliver_to | str | None | platform:target delivery spec, e.g. "telegram:123456789". Omit to skip outbound delivery. |
message | str | None | Template for the message built from the payload and sent to the agent. |
enabled | bool | True | Whether the hook is active. false returns 404. |
metadata | dict | {} | Free-form key/value pairs passed to the agent context. |
Templating
Both{{ payload.x }} (Jinja-style) and {x} (format-string style) are accepted:
- The leading
payload.is optional —{{ payload.from }}and{from}resolve identically. - Missing keys render as empty strings — the template never raises on partial payloads.
- Single-pass substitution — a payload value containing
{...}is never re-expanded (injection-safe).
Actions
"agent" (default) — runs a full agent turn with the rendered message as the user input:
"wake" — nudges an existing session (triggers proactive delivery or a scheduled check-in) without injecting a new user message:
Idempotency & Retries
The gateway deduplicates concurrent and retried deliveries:- When
idempotency_keyis set, the key is rendered from the payload and hashed asSHA-256(path + "\x00" + rendered_key). - When unset, the entire payload is JSON-canonicalized and hashed.
- The key is reserved in-flight atomically — concurrent identical POSTs dedup across the await boundary.
- The key is committed only after a successful agent run — transient failures remain retryable.
External services that retry on timeout (e.g. GitHub webhooks, Linear) are safe to point directly at inbound hooks without additional dedup logic on your side.
Security
| Behaviour | Detail |
|---|---|
| Auth required | 401 on missing or invalid Authorization: Bearer header |
| Per-hook auth | auth field on the hook overrides the gateway’s global auth_token |
| Malformed JSON | 400 response, no agent run |
| Missing agent | 500 when the hook names an agent that isn’t registered (no silent fallback to another agent) |
| Disabled hook | 404 when enabled: false |
End-to-End Example: Gmail → Triage Agent → Telegram
- Gmail push subscription fires
POST /hooks/gmailwith the email payload. - Gateway verifies the bearer token from
$GMAIL_HOOK_SECRET. - Session key
gmail:<message_id>scopes the conversation to this email thread. - The rendered message is sent to the
email-triageragent. - The agent’s reply is delivered to Telegram chat
123456789.
Best Practices
Always set an idempotency_key for event-driven sources
Always set an idempotency_key for event-driven sources
External webhooks retry on timeout. Without an
idempotency_key, a slow agent run followed by a timeout retry will run the agent twice. Use a message or event id from the payload.Use per-hook auth tokens, not the global token
Use per-hook auth tokens, not the global token
Set a distinct
auth secret per hook so you can rotate individual secrets without restarting the gateway or changing the global token.Set session_key to scope conversations
Set session_key to scope conversations
Without
session_key, all deliveries to a hook share the same session (hook:<path>). Use a payload field like {user_id} or {message_id} to isolate conversations by sender or thread.Test locally with curl before connecting a real service
Test locally with curl before connecting a real service
Related
Webhook Verification
HMAC signature verification for outbound bot webhooks (different surface)
Proactive Delivery
Delivery routing — the channel:target format used in deliver_to
Gateway Overview
Gateway configuration, channels, and multi-bot mode
Gateway CLI
All gateway CLI commands including hooks add / list / remove

