Appservices #

An appservice (short for "application service") is Matrix's built-in extension mechanism. An appservice is an external program that the homeserver trusts to:

  • Create and act as virtual users inside a namespace it owns (e.g., @slack_*:your-server)
  • Create and act as virtual rooms inside a namespace it owns (e.g., #_discord_*:your-server)
  • Receive a push-stream of all events happening in the homeserver that touch its namespace

Every bridge you read about in the Bridges section is, under the hood, an appservice. But you can also write your own — for custom bots, integrations, analytics, admin tooling or bespoke bridges to networks Meldry doesn't ship by default.

This page explains the concept, shows you when you'd use an appservice vs a simple bot, and walks through registering one on a Meldry workspace.

Appservice vs bot #

Plain botAppservice
IdentityOne Matrix user accountAn entire namespace of virtual users (e.g., @myint_*)
AuthenticationAccess token, same as any userTwo shared-secret tokens (as_token, hs_token)
Receives eventsOnly events the bot user seesAll events matching the AS namespaces, pushed via HTTP
Creates usersNo — bot is a single userYes — on-demand, no registration flow
Creates roomsYes, like a userYes, and it can create "alias-only" namespaced rooms
Typical use!weather bot, reminder bot, GitHub notifierBridge, bulk-integration, bot farm, admin dashboard

Rule of thumb: if your integration only needs one identity and reacts to messages directed at it, use a bot. If you need many identities (one per external user) or need to see every message in a room without being in it, you need an appservice.

The registration.yaml file #

An appservice is registered with the homeserver via a registration file — a short YAML document that declares:

  • An ID (unique string)
  • A URL where the homeserver should push events
  • Two long random tokens: as_token (appservice → homeserver auth) and hs_token (homeserver → appservice auth)
  • A sender localpart (the "default" user for this appservice)
  • A list of namespace patterns the appservice reserves: users, aliases and rooms

Example #

yaml
id: meldry-weatherbot
url: https://weather.example.com/_matrix
as_token: as_7f3a... # a long random string you generate
hs_token: hs_b1c9... # another long random string
sender_localpart: weatherbot
rate_limited: false
namespaces:
  users:
    - exclusive: true
      regex: '@weather_.*:your-name\.meldry\.com'
  aliases:
    - exclusive: true
      regex: '#weather_.*:your-name\.meldry\.com'
  rooms: []

Exclusive namespaces mean nobody else on the homeserver can register a user or alias matching the regex. Be specific — don't claim @.*:your-server or you'll block normal user registration.

Registering an appservice on Meldry #

Step 1 — Generate tokens #

Use any random-secret generator. A common pattern:

bash
openssl rand -hex 32   # run twice, once for as_token, once for hs_token

Save both tokens. They never get regenerated by themselves — if you lose them, you have to re-register.

Step 2 — Write registration.yaml #

Based on the example above, adapt the fields to your integration. Make the id something human-readable and the url a reachable HTTPS endpoint — the Meldry homeserver will POST events there.

Step 3 — Upload to Meldry #

From your Meldry dashboard:

  1. Server → Appservices → Register new.
  2. Paste the full YAML, or upload the file.
  3. Click Register.

Meldry validates the YAML (namespaces must not clash with existing bridges, the URL must resolve), writes it to the homeserver config, and restarts the homeserver to pick it up. Takes about 10 seconds.

Step 4 — Start your appservice #

Your external program needs to:

  1. Expose an HTTP server at the URL you declared. Matrix will PUT batches of events there. The endpoint must be reachable from the Meldry cluster (public HTTPS, or a tunneling service).
  2. Authenticate inbound requests. Every PUT from the homeserver has ?access_token=<hs_token> in the query string. Reject anything without the right token.
  3. Talk back to the homeserver using the Matrix client-server API with Authorization: Bearer <as_token>. With this token you can create any user in your namespace (POST /_matrix/client/v3/register?kind=user with type: m.login.application_service), send messages as any of those users, create rooms, etc.

The reference SDK most people use is matrix-appservice-bridge for Node.js, or matrix-nio for Python. There's also a Rust crate: matrix-sdk-appservice.

Unregistering #

If you no longer want the appservice:

  1. Server → Appservices in the dashboard.
  2. Find the registration by ID and click Remove.
  3. Meldry removes the config entry and restarts the homeserver.

Any virtual users the appservice created stay in the database (and can still appear in room histories), but the appservice can no longer push events or act as them.

Plan limits #

  • Free / Starter — no custom appservices.
  • Pro — 1 custom appservice.
  • Business — unlimited custom appservices.

Meldry-managed bridges (Telegram, Slack, Discord, etc.) don't count against this quota — only appservices you register yourself.

Security considerations #

  • An appservice is trusted code. It can impersonate any user in its namespace, including admins if the namespace is loose. Treat tokens like root credentials.
  • Never commit registration.yaml to public git — it contains both tokens.
  • If you suspect a token leak, unregister the appservice and re-register with fresh tokens.
  • The rate_limited: false field disables rate limiting for traffic from the appservice. Only set this for high-volume integrations; leave it true (or omit it) by default.

Debugging #

  • Events not arriving at your endpoint: check the Meldry dashboard → Server → Logs → Appservices for HTTP error responses. Common causes: wrong URL, expired TLS, wrong hs_token.
  • Can't create users: check your namespace regex. The exclusive: true flag is required for the homeserver to allow your AS to create users with that pattern.
  • "Namespace conflict": another appservice (probably a built-in bridge) already claims overlapping users/aliases. Narrow your regex.

What's next #