Ecency chats
Integrate Ecency-hosted chat endpoints into Hive applications without running your own Mattermost instance.
Chat integration guide
This document explains how Ecency’s chat experience works and how other Hive applications can integrate with it. It focuses on how to consume the Ecency-hosted chat endpoints so Hive apps can embed chat without running their own Mattermost instance.
Architecture overview
- Mattermost as chat backend: Ecency provisions users, teams, channels, and personal access tokens (PATs) in a Mattermost instance configured through environment variables.
- Next.js API wrappers: The web app exposes
/api/mattermost/*routes that handle authentication, bootstrap tasks, and proxy calls to Mattermost using user-scoped PATs stored in a secure cookie. - Hive identity: Users authenticate with their Hive account. The bootstrap API validates the Hive
accessToken(therefreshTokenis not consumed by bootstrap — your app refreshes its ownaccessTokenand retries) before issuing a Mattermost PAT, so the chat identity is bound to — and must match — the Hive username. - Community mapping: Hive communities map to public Mattermost channels named after the community id (e.g.,
hive-123). The bootstrap flow can auto-join users to channels for the communities they subscribe to.
Realtime transport
- Websocket proxy:
/api/mattermost/websocketis a passthrough to Mattermost’s websocket upgraded connection. It upgrades on the edge runtime, forwards all frames bi-directionally, and injects the authentication challenge upstream using themm_patcookie created during bootstrap. - Auth flow: The proxy authenticates using the PAT created during bootstrap. Cookies work for same-site contexts, and external clients can now pass the PAT via
Authorization: Bearer <token>or?token=<mm_pat>if cookies are unavailable. - Error handling: Missing cookies receive
401 Unauthorized; invalid upgrades respond with400; upstream issues (e.g., Mattermost unreachable) respond with502.
Using Ecency-hosted chat endpoints
Other Hive apps can call Ecency’s /api/mattermost/* routes directly - no Mattermost infrastructure required. Key points:
- Base URL: Use the deployed Ecency web domain you target (e.g.,
https://ecency.com/api/mattermost). The same routes are available on staging instances for testing. - Cookies vs token: The routes use an httpOnly
mm_patcookie. From a same-origin browser context, usecredentials: "include". Cross-origin clients (e.g. browser extensions) cannot rely on the cookie — capturetokenfrom the bootstrap JSON response and send it asAuthorization: Bearer <token>(or?token=<token>for the websocket) on subsequent/api/mattermost/*calls. - Hive access tokens only: Your app never needs Mattermost credentials. Provide the user’s Hive
accessTokenandusernameto bootstrap (arefreshTokenfield is accepted for forward-compatibility but not consumed — bootstrap validatesaccessTokenonly); the Ecency backend handles PAT creation and all proxying.
Quickstart for Hive app integrators
- Collect Hive tokens: Obtain the user’s Ecency/Hive
accessToken(JWT) plus the Hiveusernameafter login in your app. (Keep yourrefreshTokento mint a freshaccessTokenwhen it expires — bootstrap itself only consumesaccessToken.) - Bootstrap against Ecency:
POST https://ecency.com/api/mattermost/bootstrapwith JSON{ username, accessToken, displayName?, community?, communityTitle? }andcredentials: "include". (ArefreshTokenfield is accepted but ignored — bootstrap validatesaccessTokenonly.)- The route validates the Hive
accessToken, checks it belongs tousername, creates/ensures the Mattermost user, and joins/creates the team and optional community channel. - Response (
200):{ ok: true, userId, channelId?, token }and anmm_patcookie scoped to Ecency. Thetokenmirrors the PAT for clients that cannot use cookies. - Idempotent: bootstrap is safe to call repeatedly and tolerates concurrent calls, but call it once per session then cache and reuse the
token/cookie. Re-bootstrap only when the token is missing/expired. Firing parallel/repeat bootstraps per page-load is unnecessary and wasteful. - Status codes:
400— missingusernameor invalid JSON body.401— missing/invalid/expiredaccessToken, or the token’s Hive account does not matchusername. Refresh youraccessTokenand retry.503— Ecency auth (HiveSigner) is temporarily unavailable. Retryable — back off and retry; do not force the user to re-login.502/504— the Mattermost backend is unreachable or timed out. Transient; retry with backoff.
- Call chat endpoints with the cookie: Subsequent requests to
https://ecency.com/api/mattermost/*automatically include the PAT cookie whencredentials: "include"is set, enabling channel lists, posting, reactions, searches, and direct messages. - Link back to your UI: Use returned
channelIdor search endpoints to deep-link into chat surfaces within your app (e.g., from profiles or community pages).
Realtime websocket usage
Use the websocket proxy to stream reactions, typing events, and post updates without exposing PATs to the browser:
// Requires the mm_pat cookie set by the bootstrap call above; same-origin// requests automatically include it and allow the edge function to auth upstream.const socket = new WebSocket("wss://ecency.com/api/mattermost/websocket");
socket.addEventListener("open", () => { // Mattermost websocket protocol uses typed actions; you can immediately // subscribe to channels or send ping frames after connect. socket.send(JSON.stringify({ seq: 2, action: "ping" }));});
socket.addEventListener("message", (event) => { const data = JSON.parse(event.data as string);
if (data.event === "posted") { // Handle new post payloads and update your UI accordingly. }});
socket.addEventListener("close", () => { // Implement your own reconnection strategy as needed.});Notes:
- Preferred same-site flow: rely on the
mm_patcookie and let the proxy run theauthentication_challengeupstream for you. - External-origin flow: call
POST /api/mattermost/bootstrap, readtokenfrom the JSON response, then connect towss://ecency.com/api/mattermost/websocket?token=<mm_pat>(or sendAuthorization: Bearer <token>) so browsers without cookie access can still authenticate. - If the PAT cookie or token is missing or expired, the request returns
401before upgrading. Re-run the bootstrap route to refresh it. - The websocket endpoint is same-origin, so use
wss://<your-ecency-host>/api/mattermost/websocketfor staging or production as appropriate. Third-party origins should keep using the Ecency host while passing the token explicitly.
What websocket events look like
Mattermost emits typed events over the websocket. Each frame is a JSON object with an event name, a data payload, and per-event metadata (full schema in the Mattermost websocket reference). Some of the most useful events for in-app chat surfaces:
posted: A new post or reply was created. The payload includes thepostobject (stringified JSON),channel_display_name, andteam_id. Parse thepostfield to get message text, attachments, androot_id(thread parent).post_edited: A user updated an existing message. Merge thepostJSON with your existing message state.post_deleted: A message was deleted. Thedataincludespostandchannel_display_name; rely onpost.idto remove it from the UI.reaction_added/reaction_removed: Reaction toggles withuser_id,emoji_name,post_id, andchannel_id.typing: Indicates a user is typing in a channel. Includesuser_id,channel_id, andparent_idfor thread typing.user_updated: Profile or status changes that may affect avatars or presence indicators.hello: Sent once after authentication; contains server connection info and session id. Useful to confirm the connection succeeded.
Example posted event payload structure (fields trimmed for brevity):
{ "event": "posted", "seq": 4, "data": { "channel_display_name": "hive-123", "channel_type": "O", "post": "{\"id\":\"abc123\",\"channel_id\":\"...\",\"message\":\"hello world\",\"root_id\":\"\"}", "sender_name": "alice", "team_id": "team123" }, "broadcast": { "channel_id": "CHANNEL_ID", "team_id": "team123" }}Notes when consuming events:
data.postis a JSON string; parse it before use. Themessageandroot_idfields are where you decide whether to update the main channel timeline or a thread.broadcasthints which channel or team should receive the update; you can use it to filter events if you maintain multiple channel tabs.- Some events (e.g.,
typing) do not include apostbody. Handle them separately from message mutations.
How Ecency access tokens are issued
Ecency always mints the same JWT accessToken/refreshToken pair regardless of the login provider. Hivesigner users obtain them via the OAuth callback, while other methods (Keychain, HiveAuth, or manual posting-key sign-in) go through the Ecency auth API, which returns the tokens after verifying a signed login challenge tied to the Hive posting authority.
Otherwise, you can mint an accessToken yourself by signing the standard Hivesigner-style challenge locally with the user’s posting private key during your app’s login flow. Any app can create the payload, sign it, base64url-encode it, and use that as accessToken. A minimal example inspired by our production implementation:
import { PrivateKey, cryptoUtils } from "@hiveio/dhive";
function buildHsCode( hsClientId: string, username: string, postingWif: string): string { const timestamp = Math.floor(Date.now() / 1000);
// Hivesigner-style payload: your app id/account, authors, and a timestamp. const payload = { signed_message: { type: "code", app: hsClientId }, authors: [username], timestamp, };
const message = JSON.stringify(payload); const hash = cryptoUtils.sha256(message);
// Sign the hashed message with the user's posting key. const signature = PrivateKey.fromString(postingWif).sign(hash).toString();
// Attach signature and base64url-encode the payload; HiveAuth signers can // also return a pre-built signedToken that you can reuse here. payload.signatures = [signature]; return Buffer.from(JSON.stringify(payload)).toString("base64url");}Available chat API routes
All routes live under /api/mattermost and expect the mm_pat cookie set by the bootstrap step.
Channel and membership
GET /channels: Lists channels for the current user, enriched with favorite/mute flags, unread counts, and direct-message partner metadata.POST /channels/[id]/join: Joins the user to a channel.POST /channels/[id]/leave: Leaves a channel.POST /channels/[id]/favorite:{ favorite: boolean }toggles favorite state.POST /channels/[id]/mute:{ mute: boolean }sets mute/mentions-only state.POST /channels/[id]/view: Marks the channel as viewed to reset unread counters.GET /channels/unreads: Returns aggregate unread/mention counts across channels.POST /channels/search:{ term }searches channels by name.
Messaging
GET /channels/[id]/posts: Fetches posts, user map, member info, and moderation context for a channel.POST /channels/[id]/posts:{ message, rootId? }sends a message or reply.PATCH /channels/[id]/posts/[postId]:{ message }edits a post.DELETE /channels/[id]/posts/[postId]: Deletes a post (user-scoped; admins can moderate community channels server-side).POST /channels/[id]/posts/[postId]/reactions:{ emoji, add }toggles a reaction.POST /search/posts:{ term }searches messages.
Direct messages and user search
POST /direct:{ username }opens/creates a direct message channel with a user by Hive username.GET /users/search?q=: Searches users by username/full name for mentions/DMs.GET /users/[userId]/image: Proxies Mattermost profile images using configured base URL (no auth cookie needed).
Community-specific behavior
- During bootstrap, Ecency pulls the user’s Hive community subscriptions and ensures corresponding Mattermost channels exist, adding the user to each. This keeps chat channels aligned with Hive community membership automatically.
- Moderation context for community channels is resolved via the Hive bridge API so moderators (owner/admin/mod roles) can delete chat posts using server-side admin privileges.
Integration checklist for other Hive apps
- Use Ecency’s production or staging base (
https://ecency.com/api/mattermost) rather than hosting Mattermost yourself. - After Hive login, call the bootstrap endpoint with
credentials: "include"so the PAT cookie is set in the browser. - Issue all chat requests against Ecency’s
/api/mattermost/*routes; they proxy to Mattermost and enforce authorization via the cookie. - Use channel search and direct message endpoints to link chat features from profiles, community pages, or content detail screens without handling Mattermost tokens directly.
- If you manage Hive community subscriptions, pass
communityto the bootstrap call so users land directly in the relevant channel after sign-in.
Security notes
- PAT cookies are httpOnly,
sameSite=lax, and secured in production. Avoid exposing admin tokens client-side; all Mattermost admin operations stay server-side. - Hive JWT validation ensures only the authenticated Hive account can bootstrap and obtain a chat PAT tied to their username.
Self-host / Ecency instances
If you self-host Ecency, set these variables in your .env (see apps/web/.env.template):
MATTERMOST_BASE_URL: Mattermost server REST base (e.g.,https://mattermost.example.com/api/v4).MATTERMOST_ADMIN_TOKEN: Admin personal access token used to provision users, teams, and channels.MATTERMOST_TEAM_ID: Team id where chat channels live.
If you’re self-hosting the Mattermost Team Edition (community edition) for development, you can start with a simple docker-compose.yml like:
version: '3.8'
services: db: image: postgres:15 restart: always environment: POSTGRES_USER: mattermost POSTGRES_PASSWORD: chat_password POSTGRES_DB: mattermost volumes: - db_data:/var/lib/postgresql/data
mattermost: image: mattermost/mattermost-team-edition:10.11 restart: always depends_on: - db ports: - "8065:8065" environment: MM_SQLSETTINGS_DRIVERNAME: postgres MM_SQLSETTINGS_DATASOURCE: postgres://mattermost:chat_password@db:5432/mattermost?sslmode=disable&connect_timeout=10 MM_SERVICESETTINGS_SITEURL: https://chat.ecency.com MM_SERVICESETTINGS_LISTENADDRESS: :8065 MM_LOGSETTINGS_ENABLECONSOLE: "true" MM_FILESETTINGS_DRIVERNAME: local MM_FILESETTINGS_DIRECTORY: /mattermost/data MM_EMAILSETTINGS_SMTPSERVER: mail.ecency.com MM_EMAILSETTINGS_SMTPPORT: "587" MM_EMAILSETTINGS_SMTPPASSWORD: email_password MM_EMAILSETTINGS_ENABLESMTPAUTH: "true" MM_EMAILSETTINGS_ENABLESECURESMTP: "true" MM_EMAILSETTINGS_REQUIREEMAILVERIFICATION: "false" MM_SERVICESETTINGS_ENABLEGIFS: "false" MM_SERVICESETTINGS_ENABLEOPENGRAPH: "false" volumes: - app_data:/mattermost/data labels: - "traefik.enable=true" - "traefik.http.routers.mm.rule=Host(`chat.ecency.com`)" - "traefik.http.routers.mm.entrypoints=websecure" - "traefik.http.routers.mm.tls=true" - "traefik.http.routers.mm.tls.certresolver=le" - "traefik.http.services.mm.loadbalancer.server.port=8065"
reverse-proxy: image: traefik:v3.0 restart: always command: - "--providers.docker=true" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.le.acme.httpchallenge=true" - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web" - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik_data:/letsencrypt labels: - "traefik.enable=true"
volumes: db_data: app_data: traefik_data:Update MM_SERVICESETTINGS_SITEURL, SMTP values, and Traefik host/ACME email to match your environment. The exposed 8065 port allows direct Mattermost access; you can remove it if you only want Traefik handling ingress.
THESE ARE REQUIRED ONLY when running the Ecency API/instance yourself; consumers of ecency.com/api/mattermost or Hive app integrators DO NOT NEED them.