//Publishing an App to the Marketplace

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 Content-Type: application/json

{ "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