Skip to main content
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.
Inbound hooks expose a 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.
from praisonaiagents import Agent
from praisonai.gateway import Gateway

agent = Agent(name="triage", instructions="Triage inbound alerts and summarise the action needed.")
gw = Gateway(agents=[agent])
gw.register_hook({"path": "alerts", "agent": "triage", "auth": "my-shared-secret"})
gw.start()
The user POSTs JSON to /hooks/<path>; the gateway authenticates the payload, runs the mapped agent, and delivers the reply to the configured channel.

Quick Start

1

Define a hook and start the gateway

from praisonai.gateway import Gateway
from praisonaiagents import Agent

agent = Agent(
    name="triage",
    instructions="Triage the inbound alert and summarise the action needed."
)

gw = Gateway(agents=[agent])
gw.register_hook({
    "path": "alerts",
    "agent": "triage",
    "auth": "my-shared-secret",
    "deliver_to": "telegram:123456789",
    "message": "Alert from {{ payload.source }}: {{ payload.title }}",
})
gw.start()
2

Point your external service at the hook URL

curl -X POST https://your-gateway/hooks/alerts \
  -H "Authorization: Bearer my-shared-secret" \
  -H "Content-Type: application/json" \
  -d '{"source": "Sentry", "title": "NullPointerException in prod"}'
3

The agent replies to Telegram automatically

No polling, no webhook handler code — the gateway does it all.

How It Works


Three Ways to Register a Hook

from praisonai.gateway import Gateway
from praisonaiagents import Agent

agent = Agent(
    name="email-triager",
    instructions="Triage the email and decide on an action."
)

gw = Gateway(agents=[agent])

gw.register_hook({
    "path": "gmail",
    "agent": "email-triager",
    "action": "agent",
    "auth": "shared-secret",
    "session_key": "hook:gmail:{from}",
    "idempotency_key": "{message_id}",
    "deliver_to": "telegram:123456789",
    "message": "New mail from {{ payload.from }}: {{ payload.subject }}",
})

gw.start()

HookConfig Options

OptionTypeDefaultDescription
pathstrrequiredURL segment after /hooks/. "gmail" exposes POST /hooks/gmail. Must be non-empty.
agentstrNoneAgent name/id to run. Falls back to the gateway’s first registered agent.
actionstr"agent""agent" runs a new turn; "wake" nudges an existing session without a new message.
authstrNoneBearer token required on inbound requests. Falls back to the gateway’s global auth_token.
session_keystrNoneTemplate for the session id, e.g. "hook:gmail:{message_id}". Defaults to "hook:{path}".
idempotency_keystrNoneTemplate for the dedup key. When unset, hashes the entire payload.
deliver_tostrNoneplatform:target delivery spec, e.g. "telegram:123456789". Omit to skip outbound delivery.
messagestrNoneTemplate for the message built from the payload and sent to the agent.
enabledboolTrueWhether the hook is active. false returns 404.
metadatadict{}Free-form key/value pairs passed to the agent context.

Templating

Both {{ payload.x }} (Jinja-style) and {x} (format-string style) are accepted:
message: "New mail from {{ payload.from }}: {{ payload.subject }}"
session_key: "hook:gmail:{message_id}"
Rules:
  • 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:
action: agent
message: "Triage: {{ payload.title }}"
"wake" — nudges an existing session (triggers proactive delivery or a scheduled check-in) without injecting a new user message:
action: wake
session_key: "daily:{{ payload.user_id }}"

Idempotency & Retries

The gateway deduplicates concurrent and retried deliveries:
  1. When idempotency_key is set, the key is rendered from the payload and hashed as SHA-256(path + "\x00" + rendered_key).
  2. When unset, the entire payload is JSON-canonicalized and hashed.
  3. The key is reserved in-flight atomically — concurrent identical POSTs dedup across the await boundary.
  4. 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

Authorization: Bearer <token> is the only accepted form. The ?token= query-parameter path was removed because it leaks the shared secret into access logs.
BehaviourDetail
Auth required401 on missing or invalid Authorization: Bearer header
Per-hook authauth field on the hook overrides the gateway’s global auth_token
Malformed JSON400 response, no agent run
Missing agent500 when the hook names an agent that isn’t registered (no silent fallback to another agent)
Disabled hook404 when enabled: false

End-to-End Example: Gmail → Triage Agent → Telegram

hooks:
  - path: gmail
    agent: email-triager
    action: agent
    auth: ${GMAIL_HOOK_SECRET}
    session_key: "gmail:{message_id}"
    idempotency_key: "{message_id}"
    deliver_to: telegram:123456789
    message: |
      From: {{ payload.from }}
      Subject: {{ payload.subject }}
      Snippet: {{ payload.snippet }}
      
      Triage this email and suggest a one-line action.
  1. Gmail push subscription fires POST /hooks/gmail with the email payload.
  2. Gateway verifies the bearer token from $GMAIL_HOOK_SECRET.
  3. Session key gmail:<message_id> scopes the conversation to this email thread.
  4. The rendered message is sent to the email-triager agent.
  5. The agent’s reply is delivered to Telegram chat 123456789.

Best Practices

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.
Set a distinct auth secret per hook so you can rotate individual secrets without restarting the gateway or changing the global token.
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.
curl -X POST http://localhost:8765/hooks/gmail \
  -H "Authorization: Bearer my-shared-secret" \
  -H "Content-Type: application/json" \
  -d '{"from": "alice@example.com", "subject": "Hello", "message_id": "test-001"}'

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