Skip to content

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 (the refreshToken is not consumed by bootstrap — your app refreshes its own accessToken and 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/websocket is 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 the mm_pat cookie 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 with 400; upstream issues (e.g., Mattermost unreachable) respond with 502.

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_pat cookie. From a same-origin browser context, use credentials: "include". Cross-origin clients (e.g. browser extensions) cannot rely on the cookie — capture token from the bootstrap JSON response and send it as Authorization: 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 accessToken and username to bootstrap (a refreshToken field is accepted for forward-compatibility but not consumed — bootstrap validates accessToken only); the Ecency backend handles PAT creation and all proxying.

Quickstart for Hive app integrators

  1. Collect Hive tokens: Obtain the user’s Ecency/Hive accessToken (JWT) plus the Hive username after login in your app. (Keep your refreshToken to mint a fresh accessToken when it expires — bootstrap itself only consumes accessToken.)
  2. Bootstrap against Ecency:
    • POST https://ecency.com/api/mattermost/bootstrap with JSON { username, accessToken, displayName?, community?, communityTitle? } and credentials: "include". (A refreshToken field is accepted but ignored — bootstrap validates accessToken only.)
    • The route validates the Hive accessToken, checks it belongs to username, creates/ensures the Mattermost user, and joins/creates the team and optional community channel.
    • Response (200): { ok: true, userId, channelId?, token } and an mm_pat cookie scoped to Ecency. The token mirrors 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 — missing username or invalid JSON body.
      • 401 — missing/invalid/expired accessToken, or the token’s Hive account does not match username. Refresh your accessToken and 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.
  3. Call chat endpoints with the cookie: Subsequent requests to https://ecency.com/api/mattermost/* automatically include the PAT cookie when credentials: "include" is set, enabling channel lists, posting, reactions, searches, and direct messages.
  4. Link back to your UI: Use returned channelId or 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_pat cookie and let the proxy run the authentication_challenge upstream for you.
  • External-origin flow: call POST /api/mattermost/bootstrap, read token from the JSON response, then connect to wss://ecency.com/api/mattermost/websocket?token=<mm_pat> (or send Authorization: Bearer <token>) so browsers without cookie access can still authenticate.
  • If the PAT cookie or token is missing or expired, the request returns 401 before upgrading. Re-run the bootstrap route to refresh it.
  • The websocket endpoint is same-origin, so use wss://<your-ecency-host>/api/mattermost/websocket for 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 the post object (stringified JSON), channel_display_name, and team_id. Parse the post field to get message text, attachments, and root_id (thread parent).
  • post_edited: A user updated an existing message. Merge the post JSON with your existing message state.
  • post_deleted: A message was deleted. The data includes post and channel_display_name; rely on post.id to remove it from the UI.
  • reaction_added / reaction_removed: Reaction toggles with user_id, emoji_name, post_id, and channel_id.
  • typing: Indicates a user is typing in a channel. Includes user_id, channel_id, and parent_id for 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.post is a JSON string; parse it before use. The message and root_id fields are where you decide whether to update the main channel timeline or a thread.
  • broadcast hints 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 a post body. 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.
  • 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 community to 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_SMTPUSERNAME: [email protected]
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.