Publishing an App to the Marketplace
This tutorial walks through publishing a new app on ButtrBase from scratch — registering it, implementing the provision webhook, adding a marketplace listing, and granting a teammate app admin access.
Prerequisites
- An active ButtrBase org with at least one app credential
- A publicly reachable HTTPS endpoint for your provision webhook
- (Optional) An AWS, Azure, or GCP marketplace product ID if you want to list there
---
Step 1 — Publish the App
~~~http
POST /api/marketplace/apps
Authorization: Bearer
{ "name": "DataSync Pro", "slug": "datasync-pro", "description": "Real-time data synchronisation for SaaS teams.", "deployment_modes": ["multi_tenant"], "pricing_model": "seat", "base_price_cents": 1500, "provision_webhook": "https://api.datasync.example/buttrbase/provision" } ~~~
Save the webhook_secret from the response — it is returned once and you will
need it to verify incoming webhook signatures.
The app is created in draft status and is not visible in the marketplace until
a platform admin approves it.
---
Step 2 — Implement the Provision Webhook
ButtrBase calls your webhook on every install lifecycle event. Your endpoint must:
1. Verify the X-ButtrBase-Signature header
2. Handle the event type
3. Return 200 with an optional endpoint_url
~~~typescript import { createHmac } from 'crypto'; import express from 'express';
const app = express(); app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf; } }));
app.post('/buttrbase/provision', (req, res) => { const sig = req.headers['x-buttrbase-signature'] as string; const expected = 'sha256=' + createHmac('sha256', process.env.WEBHOOK_SECRET!) .update((req as any).rawBody) .digest('hex');
if (sig !== expected) return res.status(401).end();
const { event, install_id, buyer_org, credentials } = req.body;
if (event === 'install.provisioning') { // Store credentials for this install await db.installs.create({ id: install_id, org: buyer_org, bbClientId: credentials.client_id, bbClientSecret: credentials.client_secret, // store securely });
// Provision a tenant slot (or spin up an instance for single_tenant) const endpointUrl = await provisionTenant(install_id, buyer_org);
return res.json({ endpoint_url: endpointUrl }); }
if (event === 'install.suspended') { await suspendTenant(install_id); }
if (event === 'install.deprovisioning') { await destroyTenant(install_id); }
res.status(200).end(); }); ~~~
---
Step 3 — Request Approval
Once your webhook is live and tested, contact your ButtrBase platform admin or
use the /platform-admin UI to submit the app for approval. A platform admin
will review the app and flip its status to published.
---
Step 4 — Add a Marketplace Listing (Optional)
To list on AWS Marketplace, add a listing with your product and plan IDs from AWS Partner Central:
~~~http POST /api/marketplace/apps/:app_id/listings
{ "marketplace": "aws", "external_product_id": "prod-abc123", "external_plan_id": "plan-pro" } ~~~
Repeat for azure or gcp as needed.
---
Step 5 — Grant App Admin to a Teammate
~~~http POST /api/marketplace/apps/:app_id/admins
{ "user_uuid": "usr_01abc..." } ~~~
The teammate's app_admin JWT claim will include your app UUID after their next
token refresh. They can now view installs, rotate the webhook secret, and manage
listings without being an org admin.
---
What Happens When a Buyer Installs
1. Buyer calls POST /api/marketplace/datasync-pro/install
2. ButtrBase creates an app_installs record in provisioning status
3. ButtrBase calls your webhook with install.provisioning + scoped credentials
4. Your webhook provisions a tenant slot and returns { endpoint_url: "..." }
5. ButtrBase marks the install active and stores the endpoint URL
6. Buyer polls GET /api/installs/:id and sees active + the endpoint URL