This Forge app is the GA replacement for the Atlassian Connect webhook descriptor that Atlassian closed for new installs on March 31, 2026.
This is a pull bridge:
- The Forge app subscribes to 8 Confluence events via
triggermodules (pagecreated/updated/trashed/restored/deleted, commentcreated/updated/deleted) and enqueues each event payload into Forge storage underevt:<cloudId>:<ts>:<rand>. - The Mattermost plugin periodically POSTs to the
drainweb trigger to read queued events and ack them. Requests are HMAC-SHA256 signed using a shared secret the admin sets via the one-shotregisterweb trigger. - No
permissions.external.fetchis declared. The Forge app never makes outbound calls. This keeps the install consent screen clean and removes the per-customermanifest.ymlediting the previous push design required.
Trade-off: Atlassian's Forge trigger module already has up to 3 minutes
of delivery delay, so the additional ~30s polling latency we add on the
plugin side is small in context.
We forge deploy this app once into the Mattermost Atlassian developer
account, then share a private install link. Customers do NOT run
forge deploy themselves.
npm installin this directory.forge loginandforge register(one-time, generates the app ID — paste it intomanifest.ymlunderapp.id).forge deploy --environment production.forge install --site https://<test-tenant>.atlassian.netto verify on a test tenant. For end-customer distribution, generate a private distribution link from the Atlassian developer console.
-
Confluence admin clicks the install link → app installs on their site.
-
Confluence admin runs
forge webtrigger(or reads the install logs) to get theregisteranddrainURLs. We'll wrap this in a UI Kit admin page in a follow-up. -
In Mattermost System Console under Plugins > Confluence:
- Paste the
drainURL into "Forge Drain URL". - Copy the auto-generated "Forge Bridge Shared Secret".
- Paste the
-
POST the secret to the
registerURL once:curl -X POST -H 'Content-Type: application/json' \ -d '{"secret":"<paste shared secret>"}' \ '<register-web-trigger-url>'
registeris one-shot — it refuses subsequent calls so a leaked URL can't be used to repoint the bridge. To re-register (e.g. rotate the secret), clearmm.registeredfrom Forge storage first.
The Mattermost plugin then polls drain on a ticker, verifies each
event, posts to subscribed channels, and acks drained keys so Forge can
delete them.
This directory is its own Node project, independent of the plugin's
server/ and webapp/ builds. CI lives in .github/workflows/forge-ci.yml
and only fires when forge/** changes.
npm install --omit=optional # CI install path
npm install # developer install path, pulls @forge/cli
npm run typecheck
npm run validate-manifest
npm run build
npm run ci # all of the above
npm run deploy # forge deployDirect one-to-one mapping with what the legacy Atlassian Connect descriptor
used to subscribe to (page + comment lifecycle), re-validated against
the Forge Confluence events list.
Forge collapses Connect's removed onto deleted. See
server/forge_event_mapping.go on the plugin side for the explicit mapping.
- Forge
triggerdelivery is best-effort (up to ~3 min delay, occasional drops). Connect webhooks had the same property, so we are not regressing. If drops show up in production we will add a plugin-side reconciliation poll over/wiki/api/v2/pages?sort=-modified-date. - Forge storage is wiped 28 days after uninstall — the queue is buffer, not durable state. The plugin is the system of record.
- Forge web trigger limit: 1000 req/min per app/env/context. At a 30s poll cadence, that's 2 req/min per tenant → headroom for ~500 installations per environment before throttling.
- Plugin-side Cloud 3LO OAuth (lives in
server/instance_cloud.goand the Cloud branches ofserver/user.go/server/flow.go). - Plugin-side polling loop (lives in
server/forge_poller.go). - Migration of existing Connect installs (we leave those running until Atlassian's Q4 2026 EOS).