Quick Start
Use a built-in slash-command button (zero config)
Buttons wired to slash commands fire automatically — no handler registration needed.When the user taps Get Help, the existing
/help handler fires automatically via the built-in command namespace.How It Works
| Step | What happens |
|---|---|
| Button tap | Platform sends callback_data string to your bot |
| Adapter wraps | Builds an InteractiveContext with user_id, message_id, chat_id, and platform-native objects |
| Registry decodes | decode_callback(callback_data) extracts namespace and payload |
| Handler runs | Async handler for matching namespace is called |
| Fallback | If no match or handler returns None, the fallback handler is tried |
Decoding Rules
decode_callback(data) maps raw callback_data to a (namespace, payload) tuple:
| Input | Namespace | Payload |
|---|---|---|
"cmd:help" | "command" | {"command": "help"} |
"approval:yes" | "approval" | {"value": "yes"} |
"pair:approve:telegram:123:abc:deadbeef" | "pair" | {"value": "approve:telegram:123:abc:deadbeef"} |
"plain" | "plain" | {} |
"" | "unknown" | {} |
dispatch(), the registry also writes decoded_namespace and decoded_payload into ctx.platform_data before calling your handler.
Choosing a Namespace Style
Platform-Specific Context
Each adapter populatesctx.platform_data with native objects so you can access full platform functionality inside your handler.
Telegram
Telegram
| Key | Type | Description |
|---|---|---|
update | telegram.Update | Full update object |
context | telegram.ext.ContextTypes.DEFAULT_TYPE | PTB context |
query | telegram.CallbackQuery | The callback query that triggered the action |
Discord
Discord
| Key | Type | Description |
|---|---|---|
interaction | discord.Interaction | The interaction object |
Slack
Slack
| Key | Type | Description |
|---|---|---|
body | dict | Full Block Kit action payload |
action | dict | The specific action that was triggered |
Common Patterns
- Yes / No approval
API Reference
InteractiveContext
Passed to every handler by the registry’s dispatch() method.
| Field | Type | Default | Description |
|---|---|---|---|
callback_data | str | required | Raw callback data from the platform |
user_id | str | required | Platform-native user ID |
message_id | Optional[str] | None | ID of the message containing the button |
chat_id | Optional[str] | None | ID of the chat where the click happened |
bot_adapter | Optional[BotAdapter] | None | The bot adapter handling this interaction |
platform_data | Dict[str, Any] | {} | Platform-specific objects + decoded fields after dispatch |
dispatch(), platform_data also contains:
decoded_namespace— the decoded namespace stringdecoded_payload— the decoded payload dict
InteractiveHandler
None string to mark the click as handled (stops the chain). Return None to fall through to the fallback handler.
InteractiveRegistry
| Method | Description |
|---|---|
register(namespace, handler) | Register an async handler for a namespace. Re-registering warns and overwrites. |
unregister(namespace) | Remove a handler. No-op if not registered. |
set_fallback(handler) | Register a fallback called when no namespace matches or the handler returns None. |
dispatch(context) | Decode callback_data, invoke the matching handler, try fallback. Returns True if handled. |
has_handler(namespace) | Check if a namespace has a registered handler. |
list_namespaces() | Return all registered namespace names. |
Functions
| Function | Signature | Description |
|---|---|---|
encode_action(namespace, action) | (str, PresentationAction) -> str | Encode an action for callback_data. CALLBACK → "ns:value", COMMAND → "cmd:name", URL/WEB_APP → namespace. |
decode_callback(data) | (str) -> Tuple[str, Dict] | Decode callback_data into (namespace, payload). |
create_registry() | () -> InteractiveRegistry | Preferred. Create a fresh per-adapter registry. |
Built-in Namespaces
Every platform adapter (Telegram, Discord, Slack) registers these automatically:| Namespace | What it does |
|---|---|
command | Routes cmd:<name> button clicks to the registered /name slash-command handler. Telegram also enforces command_policy. |
pair | Handles approve/deny buttons for unknown-user pairing (same behavior as before, now routed through InteractiveRegistry). |
Best Practices
Use create_registry() per adapter
Use create_registry() per adapter
Each bot adapter should have its own registry to prevent namespace collisions when running multiple bots in the same process.
Always set PRAISONAI_CALLBACK_SECRET in production
Always set PRAISONAI_CALLBACK_SECRET in production
Inline-button callbacks are HMAC-signed. Without a persistent secret, callbacks fail after every bot restart.
Return non-None only when you handled the click
Return non-None only when you handled the click
Returning
None lets the fallback handler run. This is useful when one namespace handler is shared across multiple button types and you only want to handle specific values.Catch your own exceptions inside the handler
Catch your own exceptions inside the handler
Unhandled exceptions inside a handler are logged, but the registry will still try the fallback. Users won’t see a useful error message unless you handle it yourself.
Related
Messaging Bots
Complete bot configuration and platform setup
Unknown-User Pairing
Owner-approval flow for new users using the pair namespace

