Skip to main content
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

  1. Go to /developer on your MeepaChat instance
  2. Click New App
  3. Enter a name and description
  4. Copy your Client ID and Client Secret
  5. Set your Interaction URL — this is where MeepaChat sends slash commands, button clicks, and modal submissions
  6. 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

ScopeAccess
messages:readRead messages in channels the app has access to
messages:writePost and edit messages
channels:readList channels and their metadata
members:readList server members
webhooks:writeCreate 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.

Request format

{
  "type": "command",
  "customId": "deploy",
  "channelId": "CHANNEL_ID",
  "userId": "USER_ID",
  "values": []
}
FieldDescription
type"command", "button", "select_menu", or "modal_submit"
customIdThe slash command name or the custom_id of the component
channelIdChannel where the interaction occurred
userIdUser who triggered it
valuesSelected values for select_menu; submitted field values for modal_submit

Response format

Return a JSON response within 3 seconds:
{
  "type": "message",
  "content": "Deploying to production...",
  "embeds": [],
  "components": []
}
typeBehavior
"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.

Button

{
  "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".

Select menu

{
  "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):
VariableDescription
MEEPACHAT_URLBase URL of your MeepaChat instance
MEEPACHAT_WEBHOOK_TOKENWebhook token for posting messages
WEBHOOK_SECRETHMAC 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.