Falsehoods developers believe about Connect install hooks

Falsehoods I’m going to talk about:

  • “The /installed webhook will be called exactly once, when the customer installs the app”,
  • “The base_url is the primary key”,
  • “The client_key is the primary key”,
  • “I should save any info sent through the /installed hook”
  • “Everyone uses the latest version of my app”,
  • “/installed won’t be called after the customer unsubscribes the app”

This is more of a blogpost than a question. Fellow vendors keep asking about the tricks of the /installed hooks on Connect, so I’m going to put my understanding here, and my question will be “Am I correct?”. The first who answers “You are totally incorrect, here’s why” gets all the points :wink:

IANAL, I didn’t write the specs either, please don’t trust this information blindly.

The /installed webhook will be called once”: :x: FALSE.

What is true: The /installed is called every time a Confluence/Jira instance wants to push new details about itself, to our app (Note: I use “/installed” but in reality, the URL depends on what you’ve declared in your JSON descriptor).

  • /installed is called when Atlassian rotates the secrets (which you can request, by the way),
  • /installed is called when the base_url changes,
  • /installed is called when instances want to tell you that they’re using your new descriptor. Atlassian guarantees that all apps who use the app will call /installed within 24hrs of the appearance of the new version on the Marketplace, except if the upgrade requires customer approval, and it requires customer approval when there is a scope increase or a price change.
  • /installed is called whenever Atlassian wants, actually.

You should save all the info that is provided, the only thing you can’t change is the clientKey. For example, if Atlassian provides a new secret, it will authenticate the /installed request with the old secret, but all subsequent calls will be made with the new secret, so you must save it.

I should save any info sent through the /installed hook”: :warning:

Yeah, but no:

  • The risk is that a pirate would override the details of someone else, just by sending an /installed request,
  • So, if the clientKey exist, the request must be signed with the existing secret,
  • Don’t accept to change the clientKey just because it’s signed.
  • The initial webhook is not authenticated with JWT, but it is signed with a public key just to be sure it’s really Atlassian (look at the code of ACE or ACSB to understand).

The base_url is the primary key”: :x: FALSE.

No, not at all, the base_url is merely here for information, and in fact you might not even need the base_url. Customers can change their domain name up to 3 times, so base_urls aren’t even immutable.

The client_key is the primary key”: :negative_squared_cross_mark: Yes, but no for site reimports.

You can use the client_key everywhere in your DB, sure.

BUT during a site reimport, they start with a fresh Confluence, therefore a fresh clientKey, for the same base_url. Yup. So you’ll receive a webhook for a base_url that already exists. As we said, a base_url is not a primary key.

  • By default, a new clientKey means it’s a new install,
  • You may not receive an uninstall hook for the older clientKey,
  • Maybe if the customer wants to migrate their data to the new clientKey, you may have to move their data to the new clientKey. But how do you authenticate that they owned the previous data? You can’t. Because they can’t prove that they still have access to their old instance, from before the site reimport.

I’ve lost the docs and the lengthy community threads about site reimports, but they’re worth a read.

Everyone uses the latest version of my app”: :x: FALSE.

  • After you publish a new connector, instances will ugprade within 24hrs if the change is minor,
  • You must maintain compatibility during this period of time. It’s not fun, especially if you change the domain of your app, your app must keep accepting requests at the old domain.
  • If you have performed a price change or a scope/permission upgrade, they administrators are free to upgrade. They may choose not to. I don’t know whether they have 2 months until they’re forced, or maybe they have 1 year (You must provide the app for 1 year after you decommission it, as well), or maybe this workflow is not managed at all. At one point in the past, Atlassian recommended to publish new versions on a subdomain, so you can keep the old subdomain online, but I don’t think they still recommend it.

/installed won’t be called after the customer unsubscribes the app”: :x: FALSE.

  • The app is still installed. They only unsubscribed at the billing level. So, since it’s installed, they keep sending you the /installed webhooks at every upgrade,
  • Usually, Atlassian tries to coordinate the uninstallation of the app with the unsubscription, but either of them may be unsuccessful,
  • Sometimes you miss the /uninstall webhook, and have no way to know.
  • So, you need to rely on the ?lic=true in the URL to know whether the license is still active. You can as well perform a request to the Confluence/Jira instance, to get your license status (and you get awesome details about it). You can as well put a is_addon_licensed condition in your atlassian-connect.json,
  • Customers can voluntarily keep your app installed to get their data after they leave. It’s up to you to decide which features should be available for unlicensed customers and what to hide.

There is a trick if you want to know who still has your app installed:

  • Publish a new micro change in your descriptor,
  • Atlassian picks up on it, upgrades all instances (assuming you haven’t recently made a major upgrade that customers could reject),
  • After 24hrs, you can unsafely assume that the remaining instances have uninstalled your app, and you have missed the uninstall hook (but this assumption is unsafe, I insist, don’t straight up delete data and don’t accuse me if it doesn’t work).

It’s hard to understand all the install webhook workflow :white_check_mark: Correct

It’s incredibly hard to design a distributed protocol, and this one has a lot of special cases, as it should be. So Atlassian has performed quite a good work at hardening this implementation. I’ve personally chosen to trust the Atlassian Connect Spring Boot implementation, others have chosen the Atlassian Connect Express, we can suppose Atlassian will keep maintaining them, and we can suppose when they move everyone to Forge, they’ll stop supporting AtlasKit, ACSB and ACE.

Please don’t trust my gruyère understanding, it’s full of holes. To summarize: Accept everything from the new descriptor posted at /installed, except a new clientKey, but it MUST be signed with the previous secret; base_urls are not primary keys; client_keys are primary keys but they can change in the situation of a site reimport.

Am I correct?


I feel like this might trigger a whole load of “falsehoods” posts about various parts of Connect & Forge…