Integrating External Services in Atlassian Forge — Without Losing the “Runs on Atlassian” Eligibility

Many Forge developers eventually face the same challenge: how to connect securely to an external system without losing the “Runs on Atlassian” badge.
This post shows a clean architectural pattern to achieve that — using only inbound communication and Forge’s built-in APIs.

The full demo project is available on GitHub:
Forge Health Monitor

The Architecture

The solution is based on two key capabilities that Atlassian Forge provides.

1. Safe navigation to external resources via router.navigate

Forge allows navigation to external pages using the @forge**/bridge** API:

import { router } from "@forge/bridge";

await router.navigate(registrationUrl);

When this happens, Forge automatically shows a security confirmation dialog, warning the user that they’re leaving the Atlassian environment.
Importantly, this does not require any permissions in your manifest.yml.

The key idea is that during this redirect, the app passes non-sensitive contextual data to the external backend, such as:
cloudId — the unique tenant identifier (used to separate data by tenant on the external backend)
accountId — the Atlassian account of the user (may be shared across multiple tenants)
triggerUrl — a static web trigger endpoint unique to each tenant (unauthenticated by default, so custom authorization logic must be implemented)
callbackUrl — the Forge page to return

This pattern can also be extended to support OAuth authorization flows (Google, Microsoft, or Keycloak).

2. Static Web Triggers for inbound communication

Static Web Triggers are the second key mechanism.
They allow external services to send HTTP requests into Forge — and doing so does not violate the Runs on Atlassian requirement, because the call direction is inbound, not outbound.

Web triggers don’t provide built-in authentication, so you must implement it yourself.
In this example, authentication is done using Ed25519-signed requests — it’s a minimal working approach, though it could be further improved.

What Forge Health Monitor Application Does

Forge Health Monitor is a monitoring solution that allows you to track the health status of external services directly from your Jira instance. Here’s what it does:

Core Functionality:

  • Monitors External Services - Continuously checks if your external APIs, websites, or services are running
  • Real-time Status Display - Shows current health status (ALIVE/DOWN) for all monitored services
  • User-friendly Interface - Simple Jira global page where you can view all your service statuses
  • Automatic Updates - Refreshes status every 30 seconds without manual intervention
    Here’s how it works conceptually:

How to Run the Example

  1. Clone the repository
    git clone https://github.com/vzakharchenko/Forge-Health-Monitor
    cd Forge-Health-Monitor

  2. Install dependencies
    npm install

  3. Register the app in your Atlassian Developer Console:
    forge register

  4. Start an ngrok tunnel on port 8080 — this will expose your custom backend:
    ngrok http 8080
    # Note the HTTPS URL (e.g., ``https://abc123.ngrok.app``)

  5. Set the ngrok URL as a Forge environment variable
    forge variables set BACKEND_URL https://abc123.ngrok.app

  6. Deploy the Forge app
    forge deploy

  7. Install it into your Jira instance
    forge install

  8. Start the custom backend
    cd ./customBackend
    npm install
    npm run start

  9. The app and backend are now running — open your Forge app in Jira and test the flow!

Exploring the App

  1. Open your Jira instance and launch the Forge app

  2. Click “Add Service”

  3. Confirm navigation by clicking “Continue” in the system dialog

  4. Enter any publicly available health check URL that returns HTTP 200 (no authentication required). Only http and https URLs are accepted in this demo for simplicity

  5. Click Continue, and you’ll return to the Forge app. You’ll see the new service in the list — the backend will now periodically (every 5 minutes) check the service’s availability.

Result

With this setup, we successfully integrated an external service without violating Runs on Atlassian. All outbound requests are performed by the external backend, while Forge remains completely isolated and only receives signed updates through web triggers.

16 Likes

Excellent post! I can say that our teams spent carefully consider how to give the most number of apps access to the Runs on Atlassian badge while still upholding the trust principles that our shared customers are looking for.

Also, if anybody can think of a way to tweak another platform feature to make it easier to achieve Runs on Atlassian for app developers then please let us know!

1 Like

Could you please confirm that this solution is suitable for the ‘Runs on Atlassian’ badge?

If the forge cli says that your app is Runs on Atlassian then that is the source of truth.

So route.navigate and static web triggers were designed to enable this pattern.

3 Likes

@vzakharchenko, thanks for the informative post. Much appreciated!

How do you handle invalid/outdated static web trigger URLs? This can happen when the Forge app is uninstalled, reinstalled. Is there a specific response that the custom backend can check for to detect them? Would be helpful to clean up such outdated URLs.

1 Like

Just tested this behavior — when a Forge app is uninstalled, the existing static web trigger URLs become invalid.

However, if the app is reinstalled, the same static web trigger URL remains valid again (no rotation) and starts working as before.

When calling a trigger of an uninstalled app, Forge responds with:

{
  "statusCode": 424,
  "response": {
    "timestamp": "2025-10-14T14:54:10.393+00:00",
    "path": "/x1/upHs7HD4zoPLigGLOgN3eFGiL2U",
    "status": 424,
    "error": "Failed Dependency",
    "requestId": "7929e69f-1088608"
  }
}

The 424 response is not a reliable way to detect outdated trigger URLs, since Forge can return the same code for any unexpected error, not only when the app is uninstalled.

Two possible workarounds:

  1. Error-based detection — handle requests in a try/catch and treat persistent 424 responses as “potentially uninstalled”. If this status persists for a while (or after a few retries), mark the Forge installation as inactive.
  2. Installation check — query the Forge installation list via GraphQL or the CLI (forge install list --json) to verify that the installation still exists.

The first approach is simpler and works automatically during normal request handling.

@rmassaioli It would be great if Atlassian returned a more specific error (e.g., 410 Gone) when the app has been uninstalled — that would make uninstalled app detection logic much clearer.

3 Likes