Marketplace Integration Guide
This guide covers the full picture of how apps, installs, admin roles, and cloud marketplace connectors fit together in ButtrBase.
Architecture Overview
~~~ Publisher org └── marketplace_app (draft → published) ├── deployment_modes: [multi_tenant, single_tenant, ...] ├── provision_webhook: https://... ├── marketplace_listings: [aws, azure, gcp] └── app_admin_grants: [user_a, user_b]
Buyer org └── app_install ├── status: provisioning → active ├── payment_source: direct | aws_marketplace | ... └── app_deployment └── endpoint_url: https://... ~~~
---
Admin Tiers
Three distinct admin levels govern the platform:
Platform admin — credential-based, governs the ButtrBase instance.
Approves app publications, manages vendor config, has cross-org visibility.
JWT claim: platform_admin: true.
App admin — RBAC grant scoped to one app, held by users in the publisher org.
Manages installs, webhook, listings, and delegates to teammates.
JWT claim: app_admin: ["app_uuid", ...].
Org admin — existing role, governs a buyer org's installs and users.
---
Install Lifecycle
~~~ POST /api/marketplace/:slug/install │ ▼ status: provisioning │ ▼ (ButtrBase calls provision webhook) Publisher provisions tenant │ ▼ (publisher responds { endpoint_url }) status: active │ [ payment_failed ] → status: suspended │ [ DELETE /api/installs/:id ] → status: deprovisioning → deprovisioned ~~~
---
Payment Sources
| Source | Who bills the buyer | What ButtrBase does |
|--------|-------------------|---------------------|
| direct | ButtrBase billing | Normal subscription lifecycle |
| aws_marketplace | AWS | Resolves entitlement via AWS API, meters usage |
| azure_marketplace | Azure | SaaS Fulfillment API v2 token exchange + activation |
| gcp_marketplace | GCP | Cloud Procurement API entitlement approval |
For marketplace sources, ButtrBase receives inbound webhook events from the cloud vendor and maps them onto the install lifecycle. Your provision webhook receives the same events regardless of payment source.
---
Provision Webhook Events
| Event | Trigger | Expected response |
|-------|---------|-------------------|
| install.provisioning | New install | { endpoint_url } |
| install.suspended | Payment failure or manual suspend | 200 |
| install.resumed | Payment recovered or manual resume | 200 |
| install.deprovisioning | Buyer cancels or is deprovisioned | 200 |
| install.plan_changed | Buyer upgrades or downgrades plan | { endpoint_url } (if changed) |
All events are signed with X-ButtrBase-Signature: sha256= using your
webhook_secret. Always verify the signature before processing.
---
Cloud Marketplace Setup
AWS
1. Register your SaaS product in AWS Partner Central
2. Configure the SNS subscription topic ARN via PUT /api/platform/config/aws-marketplace
3. Add a listing: POST /api/marketplace/apps/:id/listings with "marketplace": "aws"
4. When a buyer subscribes on AWS, ButtrBase receives the SNS notification,
resolves the entitlement, and creates an install with payment_source: aws_marketplace
Azure
1. Create a SaaS offer in Azure Partner Center and register your landing page URL
2. Configure the fulfillment API key via PUT /api/platform/config/azure-marketplace
3. Azure sends the buyer to your landing page with a marketplace_token
4. ButtrBase exchanges the token, activates the subscription, and creates the install
GCP
1. Create a SaaS solution in GCP Marketplace Producer Portal
2. Configure the Pub/Sub subscription via PUT /api/platform/config/gcp-marketplace
3. GCP publishes entitlement events to the subscription; ButtrBase processes them
and creates or updates installs accordingly
---
Role Check Quick Reference
~~~typescript
// After login — fetch once and cache
const me = await fetch('/api/auth/me', { headers: { Authorization: Bearer ${token} } }).then(r => r.json());
me.platform_admin // boolean me.app_admin.includes(appId) // boolean — is this user an app admin? me.entitlements // string[] — active feature slugs me.roles // string[] — org roles
// Per-request role gate (backend) const claims = await bb.verifyToken(bearer); // local, no network const check = await bb.checkEntitlement(bearer, 'feature_slug'); // remote const access = await bb.checkAppAccess(bearer, appId); // remote ~~~
---