Building an Add-on
Last updated February 06, 2024
Table of Contents
This article describes the basic steps for developing an add-on for the Heroku Elements Marketplace. For more advanced add-on development topics, see Advanced integrations.
Because add-ons are cloud services, you can develop them in whatever language and framework you prefer. Add-ons are not required to run on Heroku.
The primary technical requirements for an add-on are:
- It must be able to receive HTTPS requests.
- It must be able to parse JSON.
This article is written assuming you’re using the Add-on Partner API v3, which is the most current and what all new integrations use. If your Add-on was created before April 2018, you’ll most likely want the legacy version of this article, which documents the high level process under v1. You can find the Add-on Partner API version your add-on is using in the Partner Portal under “Settings” -> “Provisioning API”.
Understanding the add-on life cycle
Heroku communicates with your add-on service via JSON requests sent over HTTPS to endpoints you create and operate. When a customer takes an action related to an add-on, we’ll make a request to the endpoints you operate with relevant parameters. You’ll then orchestrate the necessary changes within your infrastructure to respond to those customer requests.
Add-ons are billed from when the provisioning request is sent and pro-rated to the second. Please see Usage & Billing for more details.
Actions you must implement:
- Provisioning a new instance of your add-on,
- OAuth token exchange, and
- Deprovisioning an add-on instance.
Optional actions that you may implement include:
- Plan upgrades or downgrades,
- Creating webhooks on the app associated with an add-on resource,
- Implementing SSO to allow your Heroku customers to log in to a dashboard you provide, and
- Requesting additional information about a customer or their app environment via the Platform API for Partners.
OAuth token exchange is not technically required for a basic integration, but you will be unable to use asynchronous provisioning or the Platform API for Partners without it.
Plans and many other attributes of your add-on service are configured in the Add-on Partner Portal.
Before you begin: Register as an add-on partner
First, create a free Heroku account if you do not already have one.
After you’ve created an account, visit our Partner Portal, logging in if necessary. Your access to and use of the Partner Portal is governed by the salesforce.com License and Distribution Agreement for the Heroku Elements Marketplace, and if you are domiciled in Italy, the Additional Terms for Heroku Elements Marketplace Providers. The purpose of the salesforce.com License and Distribution Agreement for the Heroku Elements Marketplace is to set up the business relationship between your company and Heroku. It covers the following:
- Distribution of your product via the Heroku Elements Marketplace.
- Intellectual property protection for both parties.
- Revenue sharing terms, including payments, reporting and audits.
- Rules for pricing changes.
- Confidentiality, allowing your company and Heroku to share details about upcoming product features, usage metrics, etc.
- Technical support and marketing responsibilities.
Be sure to familiarize yourself with the terms and conditions of the applicable agreements and policies before signing up.
Step 1: Generate your add-on manifest
Every add-on has an addon-manifest.json
file that contains metadata, configuration, and credentials for the add-on. You create your add-on manifest with Heroku’s addons-admin CLI plugin.
First ensure you’ve installed the Heroku CLI and logged in via heroku login
.
Then, install the addons-admin plugin:
$ heroku plugins:install addons-admin
You can then generate a manifest in your add-on’s root directory with the following command:
$ heroku addons:admin:manifest:generate
This will create the file addon-manifest.json
in the current working
directory. A manifest contains secrets and should not be checked into source
control.
The generated manifest is a skeleton that includes defaults and placeholder values. You’ll need to modify some attributes in the manifest to create a working add-on service.
Edit the manifest file and set the id
field to the identifier you’d like your add-on to have when developers interact with it via the CLI. This identifier will also be used in the path to your Heroku Elements page, and is immutable.
We also refer to your id
as your add-on slug
.
For example, if your product is called “MySQL-o-Matic”, you might specify mysqlomatic
for id
. Later on, you can specify a human-friendly display name and marketing copy for the Heroku Elements Marketplace through the Partner Portal.
For a comprehensive description of manifest fields, see Add-on Manifest Format.
It’s common for add-on partners to create a -staging
version of their add-on to test new features “full stack”, and point this -staging
add-on to staging versions of the integration infrastructure they manage. You might want to do this as you’re building a new add-on too, to give you a safe place to test as you reach integration milestones.
Step 2: Integrate with the Add-on Partner API
When a Heroku customer performs certain administrative actions related to your add-on (such as provisioning it or changing their current plan), Heroku sends requests to your registered endpoint so you can perform the necessary actions. These requests are part of the Add-on Partner API.
Your add-on must be configured to receive and handle Add-on Partner API requests appropriately. Please see the Add-on Partner API for guidelines on SSL, idempotency, request body validation, versioning headers, and how to handle errors.
Basic authentication
Requests from Heroku for the Add-on Partner API use basic authentication over HTTPS for all operations. You’ll need to build basic authentication into your add-on integration endpoint. The username that Heroku sends will be the id
in your manifest (the identifier you chose above) and the password will be the password
field set in your manifest.
use Rack::Auth::Basic, "Heroku API" do |username, password|
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(username), ::Digest::SHA256.hexdigest("<id from manifest>")) &
Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest("<password from manifest>"))
end
The provisioning request
When a customer makes a request to provision your add-on, Heroku sends a POST
request with a JSON payload to the /heroku/resources
endpoint at the domain you configured in your add-on manifest.
We frequently refer to add-on instances as a resource
.
When an add-on receives a provisioning request from Heroku, it must:
- Respond with a successful HTTP status code,
- Persist relevant metadata needed to identify an add-on resource uniquely,
- Provision resources,
- Exchange OAuth tokens to allow for secure scoped Platform API for Partners access, and
- Update us with relevant configuration values that will be included in the owning app’s environment.
This can be done asynchronously or synchronously at your discretion, which may depend on how long your provisioning process takes.
Example request
# POST /heroku/resources
{
"callback_url": "https://api.heroku.com/addons/01234567-89ab-cdef-0123-456789abcdef",
"name": "acme-inc-primary-database",
"oauth_grant": {
"code": "01234567-89ab-cdef-0123-456789abcdef",
"expires_at": "2016-03-03T18:01:31-0800",
"type": "authorization_code"
},
"options": { "foo" : "bar", "baz" : "true" },
"plan": "basic",
"region": "amazon-web-services::us-east-1",
"uuid": "01234567-89ab-cdef-0123-456789abcdef",
... Some fields omitted
}
Example implementation psuedocode
The example responses below assume you’re using asynchronous provisioning. Some processes can be combined and requests omitted under synchronous provisioning.
post '/heroku/resources' do
# The OAuth client secret for your add-on service can be retrieved from the partner
# portal and should be stored encrypted at rest.
oauth_client_secret = config.get('oauth_client_secret')
# Create an instance of your add-on for the app uuid in the request
resource = Resource.create!(
plan: json_params[:plan],
region: json_params[:region],
oauth_grant_code: json_params[:oauth_grant][:code],
oauth_grant_expires_at: parse_time(json_params[:oauth_grant][:expires_at]),
oauth_grant_type: json_params[:oauth_grant][:type],
heroku_uuid: json_params[:uuid] # The resource uuid used in all related requests
)
# Exchanging tokens could be done in a background job
access_token_response = ExchangeGrantCode.exchange(
grant_code: resource.oauth_grant_code,
oauth_client_secret: oauth_client_secret
)
response = {
# Unique identifier, which can (and probably should) be the UUID we provide
id: resource.heroku_uuid,
message: "Your add-on is being provisioned. It will be available shortly."
}
status 202
response.to_json
end
Example response
{
"id": "37136775-c6f6-4d29-aa6b-12d1dad4febb",
"message": "Your add-on is being provisioned. It will be available shortly."
}
You still need to update the configuration value and mark provisioning as complete to complete the asynchronous provisioning process.
The deprovisioning request
When a customer removes your add-on, Heroku sends a DELETE
request to your registered integration endpoint. This request uses HTTPS and HTTP Basic Authentication just like the provisioning request. See the deprovision section of the Add-on Partner API reference article for more details.
The DELETE
request does not include a request body. You should parse the heroku_uuid
from the URL.
Example request
DELETE /heroku/resources/:heroku_uuid HTTP/1.1
Host: <your-addon.example.com>:443
Authorization: Basic YWRkb24tc2x1ZzpzdXBlci1zZWNyZXQ=
Content-Type: application/json
Accept: application/vnd.heroku-addons+json; version=3
Example implementation
delete '/heroku/resources/:heroku_uuid' do
Resource.find_by(heroku_uuid: params[:heroku_uuid]).destroy
status 204
end
Example response
Return a 204 No Content
response to let us know you’ve successfully received and processed the request.
The plan change request (upgrade / downgrade)
When a customer changes their current add-on plan, Heroku sends a PUT
request to your add-on integration API endpoint. This request includes the plan slug the Heroku customer wants to change to.
When your add-on receives a plan change request, it should:
- Upgrade or downgrade any features and settings accordingly,
- Automatically migrate any necessary state - for instance, database contents. If this isn’t possible, be sure to document thoroughly how a customer can do this manually in your add-on’s Dev Center documentation.
- Respond with any new configuration values to use for the new plan.
Example request
PUT /heroku/resources/:heroku_uuid HTTP/1.1
Host: <your-addon.example.com>:443
Authorization: Basic YWRkb24tc2x1ZzpzdXBlci1zZWNyZXQ=
Content-Type: application/json
Accept: application/vnd.heroku-addons+json; version=3
Example implementation (Ruby)
put '/heroku/resources/:heroku_uuid' do
resource = Resource.find_by(heroku_uuid: params[:heroku_uuid])
original_plan = resource.plan
resource.change_plan(json_params[:plan])
result = {
config: {
ADDON_SLUG_URL: resource.url
},
message: "Successfully changed from #{original_plan} to #{resource.plan}"
}
result.to_json
end
Example response
{
"config": {
"ADDON_SLUG_URL": "https://user:password@service.example.com/new-url"
},
"message": "Successfully changed from original_plan to another_plan"
}
Single Sign On
Implementing a single sign-on (SSO) integration lets you provide a web UI control panel to your add-on customers. Please see our in-depth guide to implementing SSO.
Step 3: Deploy the add-on
Once your service is working properly, deploy it somewhere that is publicly accessible. It does not have to be deployed to Heroku (but it certainly can be).
When you’ve deployed the add-on, modify your addon-manifest.json
file’s base_url
and sso_url
values within the production
attribute to point to your deployed app.
Finally, push your updated addon-manifest.json
file up to Heroku in order to register your add-on. You need to be logged in to the Heroku CLI with the account that accepted the Partner Terms of Service for the add-on.
$ heroku addons:admin:manifest:push
At this point, your add-on is available for you (and only you) to provision. You can manage its details in the Partner Portal and install it via the Heroku Dashboard or CLI.
Next steps
Please see Bringing an Add-on to Market for information about managing plans and other topics related to releasing an add-on in the Heroku Elements Marketplace.
Advanced integrations
There is far more that you can do with your integration to Heroku. Check out some of the articles below to explore more advanced integrations.