Federation ​
Federation lets people on different Refract.Chat instances be friends, share direct messages and group chats, join each other's servers and call each other. It's off by default.
How it works ​
- Every instance is named by its domain (
BASE_DOMAIN), and people across instances by their address:alice@chat.example.com. - People only ever sign in at home. Their instance signs every request it makes for them with its own key, and the other instance checks it.
- Each server or conversation belongs to the instance it was made on. Only that instance changes it; others keep copies. A visiting member is bound by the server's roles and permissions like anyone else.
- Calls use the LiveKit of the instance that owns the channel.
Instances find each other through https://<domain>/.well-known/refract.json, which publishes the instance's public key. The first key seen for an instance is pinned, and only a key an admin accepts can replace it.
Turning it on ​
In app.config.yaml:
yaml
federation:
enabled: true
privateKeyPath: /data/federation/instance-ed25519.pem
allowedInstances: []
blockedInstances: []
approvalRequired: false
allowInsecureHttp: false
queueWorkers: separate
media:
prune:
daysSinceLastAccess: 30
maxOverallSizeGB: 10| Setting | What it does |
|---|---|
privateKeyPath | The instance's signing key, created on first start. Keep it on persistent storage and back it up |
allowedInstances | Empty: any instance that isn't blocked. Otherwise, only the domains listed |
blockedInstances | Domains that are always refused |
approvalRequired | Every new instance waits for an admin to approve it, unless allowedInstances names it |
allowInsecureHttp | Federate over plain HTTP. For local development only |
queueWorkers | separate (the Docker image runs dedicated workers) or inline (the API process does the work) |
media.prune | Cached copies of other instances' media are dropped after this many days unopened, or, least recently opened first, while together over this size. 0 means no limit. They're fetched again when needed |
Before you enable it:
BASE_DOMAINmust be the domain other instances reach you on, and must never change: every ID your instance creates contains it.- Serve
/.well-known/refract.jsonand/federation/v1from that domain. The server handles both, so route them to it like the rest of the site. - Losing or replacing the key means an admin on every instance you federate with has to accept the new one.
Approving instances ​
With approvalRequired, or when a known instance starts signing with a new key, it waits for an admin. Meanwhile what it sends is held, and what you send it is parked.
Instance admins decide in Manage instance → Federation. It lists every instance yours has heard from, with the fingerprint of the key it signs with (and of a new one waiting), whether the config's lists allow it, and how many of its users, servers and conversations you hold. A badge counts the instances waiting on you, and Check now reads an instance's well-known file again, sending what waits for it if it answers. The allow and block lists are shown there but set in the config.
Or decide from the command line:
sh
docker compose run --rm backend yarn cli --federation-trust chat.other.example approve| Decision | Effect |
|---|---|
approve | Accepts the new instance. Held messages are applied, and parked ones sent |
reject | Refuses the instance from now on, and discards what was held and parked |
accept-key | Accepts an instance's new key |
reject-key | Refuses the new key, and discards what was signed with it |
Add --discard-held to approve or accept-key to throw away what was held instead of applying it. Typing, presence and call updates are never held: they would be out of date by then.
GIFs, emoji, reactions and presence ​
Protocol 2.2 preserves custom emoji identity across instances and synchronizes Unicode/custom reactions through the channel owner. Received GIFs can display while local GIF search is disabled. Clients load gifgooder images directly from the source provider, whose public origin is advertised through trusted instance metadata. Custom emoji are fetched through the receiving instance. Provider credentials stay on the source, and reaction ordering and retry state are cached in Redis while federation deliveries use the existing queue.
Availability and mobile/desktop presence use ordered updates, initial refresh and periodic recovery. Status crosses instances only between accepted friends, including status messages, emoji and mobile indicators. Sharing a server or conversation does not reveal status. Manual status and per-person privacy settings continue to apply.
Profile settings also control who can find an account in search: everyone, people who share a server (the default), or nobody. Searches from another instance respect the account's choice, including exact addresses and user GUIDs. Existing friendships, conversations and server memberships remain available.
Upgrade both peers and clients for these features, and apply all pending database migrations before starting the backend. With the updated image selected, run docker compose run --rm backend npx sequelize-cli db:migrate; the supplied startup script refuses to start while migrations are pending. Older version-2 peers still exchange ordinary messages, but cannot serve the new media and presence endpoints or apply federated reactions. Missing legacy emoji identity displays as a text label.
Not yet ​
- Banners, badges and the rest of profiles across instances: only profile pictures cross for now.
- Pins and polls in shared channels.