Webhooks

Configure Slack, Discord, Microsoft Teams, Google Chat, and generic webhook channels.

How a webhook channel works

Every webhook channel, whatever the platform, is reachable at one route:

https://YOUR_HOST/webhooks/<project>/<provider>/<slug>

A GET to that URL answers the provider’s verification handshake (Pepe echoes back the challenge the platform sends when you first register the URL). A POST is an inbound event. On a POST, Pepe resolves the connection, verifies the request signature against your configured secret, parses out the message, runs the bound agent, and delivers the reply through the provider’s own API. The agent work runs in the background so the platform gets its acknowledgement immediately (providers like Meta retry a slow webhook).

There is a single generic route. Adding a new provider never adds a new endpoint.

Public host. Webhook channels need a URL the platform can reach. Expose your Pepe instance behind a reverse proxy or a tunnel, and set PEPE_PUBLIC_URL so the callback URLs the CLI prints are complete. For a quick tunnel while testing, run pepe serve --tunnel.

Slack, Discord, Microsoft Teams, Google Chat

These providers are configured through the guided setup (or the dashboard), which asks for exactly the fields each one needs and prints the callback URL to register:

pepe setup

Choose the channel option, pick the provider and the agent, and enter the credentials (a ${ENV_VAR} reference is accepted for any secret). Each has its own page with its provider-specific fields and setup steps: Slack, Discord, Microsoft Teams, Google Chat. This page covers what’s shared by all of them (and by WhatsApp).

Group @mentions

Slack, Microsoft Teams and Google Chat support group/channel conversations, where the connection answers only when @mentioned by default (a direct message always reaches the agent regardless). Set require_mention: false on the connection to answer every message in every channel it’s in. Or, without touching that connection-wide setting, waive it for a single channel from inside that channel:

/mention off   # this channel only, until /new - no @mention needed to be answered
/mention on    # back to requiring an @mention
/mention       # show the current setting

Since a channel command still has to be addressed to run in the first place, the first /mention off needs an actual @mention (@bot /mention off); after that, the channel no longer needs one until /new. The waiver lives on that channel’s own conversation, not the connection, so it never leaks into any other channel. WhatsApp and Discord don’t gate on mentions today (always answered), so /mention is a no-op there.

Switching models

The /model and /models commands let people check or change which AI model answers them. They work only on an admin-mode connection with commands enabled (see the mode comparison in Channels); on support, they are treated as plain text. /models lists the models available to the connection’s project; /model shows the current one, or changes it:

/model openrouter               # ask whether to switch just this chat or everyone
/model openrouter session       # switch for this conversation only
/model openrouter global        # switch for everyone this connection talks to

Switching globally, for everyone the connection talks to, is reserved for trainers (the same trusted list that controls memory); everyone else in an allowed conversation can only switch their own conversation. Set model_switch_locked: true on the connection to turn it off entirely for non-trainers. This is the same mechanism WhatsApp uses; Telegram’s version adds a tappable picker instead of typed commands.

Under the hood: the provider contract

Every webhook channel is one small module that implements the same contract, so they all behave consistently and a new platform is a new module rather than a new route. The callbacks are:

If you write a plugin that implements this contract, it registers as a new provider under its own name, reachable at the same /webhooks/... route with no extra wiring.