Chat apps
A chat app (a bot) is a member of Repzo Chat that your code drives. It posts messages built from Block Kit blocks, hears when people click its buttons or fill in its forms, opens modals, and can give each person a Home tab.
If you have built a Slack app, you already know most of it: the payloads, method names (chat.postMessage, views.open, …) and interaction shapes are Slack’s. The differences are listed on each reference page under How Repzo Chat differs from Slack.
Create a bot
Section titled “Create a bot”- Open Settings → Integrations & Developers → Developer Apps and choose the Bots tab.
- Create a bot. Give it a name and avatar — that’s how it appears in Chat.
- Copy its
foxa-*token when it is shown. Workstation stores only a hash and can’t show it again. - Under Interactivity, set the HTTPS URL that should receive clicks and form submissions. Copy the signing secret shown once — you need it to verify requests.
- Add the bot to the channels it should post in (public channels work without joining when the bot has
chat:write.public).
Keep the token in a secret store or environment variable. Never commit it.
Two ways to call Workstation
Section titled “Two ways to call Workstation”| Slack-compatible bot API | Public REST API v1 | |
|---|---|---|
| Base path | /api/bot/<method> |
/api/v1/... |
| Style | Slack method names and bodies, answers { "ok": true, ... } |
REST resources, OpenAPI-documented at /api/v1/docs |
| Best for | Porting Slack apps, Slack SDKs pointed at Repzo | New integrations that also use other Workstation resources |
Both take a Authorization: Bearer foxa-… header and the same Block Kit JSON.
Bot API methods
Section titled “Bot API methods”| Method | What it does |
|---|---|
POST /api/bot/chat.postMessage |
Post a message (channel, text, blocks, attachments, thread_ts) |
POST /api/bot/chat.update |
Edit a message your bot posted (ts, text, blocks, attachments) |
POST /api/bot/chat.delete |
Delete a message your bot posted (ts) |
POST /api/bot/chat.postEphemeral |
Post a message only one person sees |
POST /api/bot/views.open / views.push / views.update |
Open, stack and update modals |
POST /api/bot/views.publish |
Set a person’s Home tab |
GET /api/bot/channels.list / channels.info |
List public channels / look one up |
GET /api/bot/files/:id |
Download a file someone attached in your form (the link comes in the payload) |
channel accepts a channel id or its short name (sales or #sales).
Scopes
Section titled “Scopes”A bot’s scopes are set when it is created: chat:write.public (post in public channels), chat:write (post anywhere it’s a member), chat:write.thread, chat:update, chat:delete and channels:read.
Quick start
Section titled “Quick start”Post a message with a button:
curl -X POST https://workstation.repzo.com/api/bot/chat.postMessage \ -H "Authorization: Bearer $REPZO_BOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "channel": "sales", "text": "New deal: Acme expansion", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*New deal:* Acme expansion — $12,000" } }, { "type": "actions", "elements": [ { "type": "button", "action_id": "approve", "style": "primary", "text": { "type": "plain_text", "text": "Approve" }, "value": "deal-42" } ] } ] }'When someone clicks Approve, your interactivity URL receives a block_actions payload.
Verify requests from Repzo
Section titled “Verify requests from Repzo”Every request Workstation sends your app — clicks, form submissions, option lookups, events — is signed with your signing secret:
X-Fox-Timestamp: 1791799555X-Fox-Signature: sha256=5f2c…The signature is HMAC-SHA256(secret, "<timestamp>.<raw request body>") as hex. Recompute it over the raw body, compare in constant time, and reject requests older than five minutes.
import crypto from "node:crypto";
function isFromRepzo(rawBody, headers, secret) { const timestamp = headers["x-fox-timestamp"]; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const expected = "sha256=" + crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"); const given = headers["x-fox-signature"] ?? ""; return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));}Sandboxes
Section titled “Sandboxes”A sandbox has its own Developer Apps. Requests to your own bot endpoints are delivered from sandboxes too, so you can test an app end to end without touching the live workspace.