Full apps are interactive integrations that can install via OAuth, respond to slash commands, render buttons and select menus, open modals, and subscribe to channel events. They run as separate HTTP services — a Cloudflare Worker is the recommended host.
What apps can do
- OAuth install flow — users install your app to a server with one click
- Slash commands — respond to
/command invocations in any channel
- Buttons and select menus — render interactive components in messages
- Modals — open multi-field forms triggered by button clicks
- Event subscriptions — receive
message.created, member.joined, and other events via webhook
Create an app
- Go to
/developer on your MeepaChat instance
- Click New App
- Enter a name and description
- Copy your Client ID and Client Secret
- Set your Interaction URL — this is where MeepaChat sends slash commands, button clicks, and modal submissions
- Set your Redirect URI — the OAuth callback URL for your app
Keep your Client Secret private. It is used to verify that interaction requests genuinely come from MeepaChat.
OAuth install flow
When a user installs your app, MeepaChat runs a standard OAuth 2.0 authorization code flow.
User clicks "Install"
→ MeepaChat shows permission screen
→ User approves
→ MeepaChat redirects to your Redirect URI with ?code=...
→ Your app exchanges code for access token
→ Store token, post confirmation message
Step 1: Authorization URL
Redirect the user to:
https://chat.example.com/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://your-app.workers.dev/callback
&scope=messages:write channels:read
&response_type=code
&state=RANDOM_STATE
Step 2: Exchange code for token
curl -X POST https://chat.example.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://your-app.workers.dev/callback"
}'
Response:
{
"access_token": "...",
"token_type": "Bearer",
"scope": "messages:write channels:read",
"server_id": "SERVER_ID"
}
Store the token — use it as Authorization: Bearer TOKEN on all subsequent API calls for that server.
Scopes
| Scope | Access |
|---|
messages:read | Read messages in channels the app has access to |
messages:write | Post and edit messages |
channels:read | List channels and their metadata |
members:read | List server members |
webhooks:write | Create and manage incoming webhooks |
Interaction URL
Set your Interaction URL in the Developer Portal. MeepaChat POSTs to this URL whenever a user triggers a slash command, clicks a button, picks a select menu option, or submits a modal.
{
"type": "command",
"customId": "deploy",
"channelId": "CHANNEL_ID",
"userId": "USER_ID",
"values": []
}
| Field | Description |
|---|
type | "command", "button", "select_menu", or "modal_submit" |
customId | The slash command name or the custom_id of the component |
channelId | Channel where the interaction occurred |
userId | User who triggered it |
values | Selected values for select_menu; submitted field values for modal_submit |
Return a JSON response within 3 seconds:
{
"type": "message",
"content": "Deploying to production...",
"embeds": [],
"components": []
}
type | Behavior |
|---|
"message" | Post a new message in the channel |
"update" | Update the original message in place |
"defer" | Acknowledge immediately; post a follow-up later |
Components
Attach interactive elements to messages using the components field.
{
"components": [{
"components": [{
"type": "button",
"custom_id": "approve",
"label": "Approve",
"style": "primary"
}, {
"type": "button",
"custom_id": "reject",
"label": "Reject",
"style": "danger"
}]
}]
}
Button style values: "primary", "secondary", "danger".
{
"components": [{
"components": [{
"type": "select_menu",
"custom_id": "env_select",
"placeholder": "Choose environment",
"options": [
{ "label": "Production", "value": "prod" },
{ "label": "Staging", "value": "staging" },
{ "label": "Development", "value": "dev" }
]
}]
}]
}
Starter template
The Cloudflare Worker template in integrations/template/ is a ready-to-deploy starting point. Clone the MeepaChat repo and copy it:
git clone https://github.com/bogpad/meepachat
cp -r meepachat/integrations/template my-integration
cd my-integration
npm install
The template handles:
/health — liveness check
/install — OAuth entry point (stub, wire up your auth URL)
/webhook — receive events from external services with HMAC verification
/interaction — receive slash commands, button clicks, and modal submissions from MeepaChat
Environment variables
Set these in wrangler.toml (plain) or via wrangler secret put (sensitive):
| Variable | Description |
|---|
MEEPACHAT_URL | Base URL of your MeepaChat instance |
MEEPACHAT_WEBHOOK_TOKEN | Webhook token for posting messages |
WEBHOOK_SECRET | HMAC secret for verifying incoming webhooks from external services |
npx wrangler secret put MEEPACHAT_WEBHOOK_TOKEN
npx wrangler secret put WEBHOOK_SECRET
Example: GitHub integration Worker
// src/transform.ts
import type { MeepaChatMessage } from "./types";
export function transform(event: string, payload: unknown): MeepaChatMessage | null {
if (event === "pull_request") {
const pr = payload as { action: string; pull_request: { title: string; html_url: string; number: number }; sender: { login: string } };
if (pr.action !== "opened") return null;
return {
embeds: [{
title: `PR #${pr.pull_request.number}: ${pr.pull_request.title}`,
url: pr.pull_request.html_url,
color: "#2ea44f",
author: { name: pr.sender.login },
footer: { text: "GitHub" },
}],
thread_key: `pr-${pr.pull_request.number}`,
};
}
return null;
}
// src/interact.ts
import type { Env, InteractionData, InteractionResponse } from "./types";
export function interact(type: string, data: InteractionData, env: Env): InteractionResponse {
if (type === "button" && data.customId === "approve") {
return { type: "message", content: "Approved. Merging PR..." };
}
if (type === "button" && data.customId === "reject") {
return { type: "message", content: "Rejected." };
}
return { type: "message", content: `Unknown interaction: ${data.customId}` };
}
Deploy to Cloudflare Workers
# Authenticate (one time)
npx wrangler login
# Local dev
npm run dev
# Deploy
npm run deploy
After deploying, set your Worker URL as the Interaction URL in the Developer Portal:
https://my-integration.your-subdomain.workers.dev/interaction
Verify interaction request signatures in production. MeepaChat signs requests with your Client Secret. The template’s verifySignature utility handles HMAC-SHA256 verification — call it before processing any interaction.
Event subscriptions
Subscribe your app to channel events via the API after OAuth install:
curl -X POST https://chat.example.com/api/apps/subscriptions \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "CHANNEL_ID",
"events": ["message.created", "member.joined"]
}'
MeepaChat will POST to your Interaction URL for each matching event with type: "event" and the event payload in data.