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./hooks/<path>.
/hooks/<path>; the gateway verifies auth, runs the mapped agent, and delivers the reply on the configured channel.
Quick Start
How It Works
| Concept | YAML key | Python kwarg | CLI flag |
|---|---|---|---|
| URL segment | path | path= | positional |
| Agent to run | agent | agent= | --agent |
| Action mode | action | action= | --action |
| Bearer secret | auth | auth= | --auth |
| Session id template | session_key | session_key= | --session-key |
| Dedup key template | idempotency_key | idempotency_key= | --idempotency-key |
| Delivery target | deliver_to | deliver_to= | --deliver-to |
| Agent message template | message | message_template= | --message |
| Active? | enabled | enabled= | — |
Two Actions: agent vs wake
Choose based on whether the external event carries new content for the agent.
action: agent (default) — runs a full agent turn on the templated message. Use when the external event carries new content the agent should process.
action: wake — nudges an existing session’s _last_activity without a new user message. Use when the external event means “this session is still alive / re-deliver any pending work”.
Templating
Both placeholder styles work and render the same payload fields:| Style | Example | Used in |
|---|---|---|
{{ payload.x }} | {{ payload.message_id }} | YAML examples |
{x} | {message_id} | Python / CLI examples |
- A leading
payload.is optional —{{ payload.from }}and{{ from }}both work. - Dotted paths resolve nested keys:
{{ payload.user.email }}→{user: {email: "x"}}. - Missing keys render as empty strings — templates never raise.
- Substitution is single-pass — payload values containing
{...}are not re-expanded (prevents key-corruption via payload injection).
session_key→"hook:gmail:abc123"idempotency_key→"abc123"message→"New email from alice@example.com: Hello"
Idempotency & Retries
idempotency_keyis a template; the rendered value is hashed (sha256) and scoped bypathso the same id on different hooks never collide.- If omitted, the entire payload is hashed deterministically (canonical JSON).
- Store is bounded (10,000 entries) with a 24h TTL — pruned lazily on each delivery.
- Key is only recorded on success — transient failures stay retryable.
- Concurrent identical deliveries are deduplicated atomically (in-flight reservation prevents TOCTOU between seen-check and record).
- Duplicate response:
200 {"ok": true, "deduplicated": true}.
Authentication
- Per-hook bearer: set
auth:to a literal token or${ENV_VAR}in YAML. - Falls back to the gateway’s
auth_tokenwhen no hook-specific secret is set. - Bearer header only —
?token=query params are rejected by design (prevents secret leakage into access logs). - Compared in constant time (
secrets.compare_digest). 401if no token provided,403if token is wrong.
Delivery
deliver_to: "channel:target"— e.g."telegram:123456789","discord:987654321","slack:U12345".- Reuses the same channel-bot send path as scheduled delivery (hooks and scheduler route outbound identically).
- If the channel bot is not registered, delivery is logged as failed and the hook returns
{"ok": false}so the sender retries. - Omit
deliver_toto skip outbound delivery entirely — the agent still runs.
Config Locations & Hot-Reload
YAML hooks can live at the top level (hooks:) or nested under gateway: (for grouping with other gateway settings):
reload_config — a config reload clears and re-registers the entire hook table, so removed hooks and rotated secrets take effect without a process restart. See Gateway Hot-Reload.
Real User-Interaction Flow
Gmail received a new email. Zapier POSTs the parsed message to/hooks/gmail. The gateway deduplicates bymessage_id, runs theassistantagent on a templated summary, and Telegram chat123456789gets a notification with the agent’s reply. If Zapier retries the same delivery, the gateway returns{"ok": true, "deduplicated": true}instantly without re-running the agent.
Common Patterns
GitHub Issue Triage
#triage.
Stripe Payment Event
CI Failure Ping
Best Practices
Pick a stable idempotency_key from the payload
Pick a stable idempotency_key from the payload
Use the provider’s native delivery id (Gmail
message_id, GitHub X-GitHub-Delivery, Stripe event id). Never rely on time of receipt — providers retry with identical ids.Use one per-hook auth secret per provider
Use one per-hook auth secret per provider
A separate
auth: token per integration means a leaked secret isolates to one source, not your entire webhook surface.Keep session_key templates payload-derived
Keep session_key templates payload-derived
Derive the session key from a stable entity id (
customer, repo, user) so related events thread through the same conversation and the agent has full context.Prefer action: wake when the event has no new content
Prefer action: wake when the event has no new content
If the webhook just signals “still alive” or “payment completed” without carrying content the agent should read,
action: wake saves an LLM call.Related
Gateway Overview
Gateway architecture and how channels, agents, and routing connect.
Gateway CLI
Full CLI reference including the
praisonai gateway hooks subcommand.Bot Lifecycle Hooks
In-process outbound hooks (GATEWAY_START, SESSION_START, SCHEDULE_TRIGGER) — the counterpart to inbound HTTP triggers.
Gateway Hot-Reload
How hook changes and rotated secrets take effect without a process restart.

