Marketplace Service — Platform Overview & Integration Guide
This guide explains what the Marketplace Service is, how to install it locally, how connectors are registered and enabled, and how consuming platform services receive effective configuration at runtime.
Overview
The Marketplace Service is the platform's app registry and connector marketplace. Think of it as the "app store" for the Equinox commerce platform: it holds a catalogue of apps (also called connectors) that extend or plug capabilities into other platform services (cart, PIM, OMS, subscription, user, account, etc.), and it governs their whole lifecycle — from registration and approval, through per-business and per-store enablement, layered configuration, versioning, health checks and audit, all the way to deprecation and archival.
A newcomer can picture it like this:
- A provider (in-house team or a third party) registers an app. The app declares how the platform reaches it (a deployment: an AWS Lambda, a bundled npm/Maven connector artifact, or an external HTTP service), what interfaces it implements (e.g.
PaymentIntegrationV1), and what capabilities it offers (e.g.PAYMENT). - An admin moves the app through an approval workflow (
draft→pending→approved), then enables it for a specific business and store, optionally as the default app for an interface. - Once enabled and configured, other platform services discover the app and its effective configuration (business + store + service override layers merged together) and invoke it. Marketplace publishes
app.*events on EventBridge so those services keep their local copies in sync.
Important: Marketplace does not process checkout, payments, or shipping itself. It is the registry and configuration hub — consuming services (cart, user, account, etc.) resolve which connector to call and with what settings.
Service at a Glance
| Property | Value |
|---|---|
| Purpose | App registry and connector marketplace — other services discover and invoke pluggable integration apps at runtime |
| Service name | eq-marketplace-service |
| API prefix | /v1 |
| Framework | Fastify 5.x + TypeScript |
| Database | MongoDB 6.x |
| Cache | Redis 6+ (optional) |
| Auth | AWS Cognito JWT / test tokens (local dev) |
| Health check | GET /health |
| Events | AWS EventBridge (app.*, business.app.*) |
| Consumer SDK | @equinox/marketplace-integration (eq-component/eq-marketplace-integration) |
Architecture
High-Level Component View
Connector Installation & Sync
After an admin PUTs a store override with instanceId or serviceInstanceMap, marketplace publishes app.selection.synced. Store enable must be performed only after the override is applied.
Configuration Layer Hierarchy
Deployment Types
| Type | Description | Use case |
|---|---|---|
| Lambda | Serverless function | Third-party integrations, isolated execution |
| Bundled | NPM module in consumer service | In-house platform connectors (@equinox/eq-cart-apps, etc.) |
| External | HTTP API | Third-party REST APIs |
Centralized App Selection Model
Marketplace is the single source of truth for app registration, enablement, and per-instance selection.
App Selection Flow
1. Create Interface → 2. Register App → 3. Approve App → 4. Business Enable
→ 5. PUT Override (triggers app.selection.synced) → 6. Store Enable → 7. Verify
Capability-Specific Install Rules
All connectors follow the same enablement order: business enable → PUT override → store enable → verify. Store enable must not be done before the override is applied.
| Capability | Override required before store enable? | Contract validation? | Notes |
|---|---|---|---|
| PAYMENT | Yes (paymentProvider in override) |
Yes (payment-v1.json) |
May use two override PUTs (provider config, then instanceId) |
| USER_PROFILE | Yes | No | Single override with instanceId |
| NOTIFICATION | Yes | Varies | Uses serviceInstanceMap for multi-service |
| CATALOG / INVENTORY / ORDER | Yes (business settings in override) | Via interfaces | See PIM guide |
Key Concepts
| Term | Meaning |
|---|---|
supportedServices |
Which consumer services receive events and may hold a selection. Values: cart, user, pim, order, account, etc. |
capabilities / adapterCategory |
Connector type in UPPERCASE — lookup key in marketplace_selections (e.g. PAYMENT, USER_PROFILE, CATALOG). Derived from app.capabilities[0]. |
instanceId / collectionId |
Per-service tenant ID from foundation store associations. This is the selection lookup key used by adapters (getSelectedApp(instanceId, capability)). |
serviceInstanceMap |
Routes one override to multiple services, each with its own instanceId: { "cart": "<cartCollectionId>", "user": "<userCollectionId>" }. |
configurationSchema |
Effective connector config sent in sync events. Connectors read this field at runtime — not the deprecated settings field. |
appSnapshot |
Frozen app metadata (name, version, deployment, endpoints, auth) included in sync events. |
Schema default values |
Defined at app registration; merged into the event payload when omitted from the override PUT. |
serviceIdentity |
Consumer service's identity in the SDK plugin (e.g. cart, user, account) — used to filter events and resolve serviceInstanceMap. |
Resolving instanceIds (collectionIds)
Each service has its own instance ID for a store. Resolve them from foundation-service before calling the override API:
GET {FOUNDATION}/v1/stores/{storeId}/effective
Read serviceAssociations (or equivalent) — each entry has an associationType (service name) and collectionId (the instanceId for that service).
Example (dev):
| Service | collectionId (example) |
|---|---|
| cart | 6a5f2ec09438bfc44392c8cd |
| user | 6a5f2ebc6dc9464e559df225 |
| pim | 6a5f2ebe98e8e7574ee0ef3f |
How Consuming Services Receive Apps
Consuming services integrate via @equinox/marketplace-integration. The SDK's MarketplaceEventHandler listens to EventBridge events and maintains two local MongoDB collections:
| Collection | Populated by | Purpose |
|---|---|---|
enabled_apps |
app.enabled, app.disabled, app.config.* |
Zero-hop runtime discovery — which apps are active for a store |
marketplace_selections |
app.selection.synced |
Adapter lookup — getSelectedApp(instanceId, adapterCategory) returns app + config |
Event Types Consumers Handle
| Event | What it does |
|---|---|
app.registered / app.updated / app.version.published |
Updates local app projection (filtered by supportedServices) |
app.enabled / app.disabled |
Toggles store enablement in enabled_apps |
app.config.created / app.config.updated |
Applies layered override config |
app.selection.synced |
Writes full selection to marketplace_selections + propagates configurationSchema to enabled_apps |
The app.selection.synced event is published when a PUT override includes instanceId (single service) or overrides.serviceInstanceMap (multi-service). The SDK resolves the correct instanceId per serviceIdentity and skips events where the service is not in supportedServices.
SDK location: eq-component/eq-marketplace-integration/src/events/MarketplaceEventHandler.ts
Consumer Service Prerequisites
Before a service can receive marketplace selections, ensure:
| Requirement | Details |
|---|---|
| SDK package | @equinox/marketplace-integration installed (version per service; e.g. account ≥ 1.0.30) |
| Plugin registration | marketplaceHostPlugin with correct serviceIdentity (cart, user, account, …) |
| EventBridge rule | Routes source: marketplace-service, detail-type prefix app. to the service Lambda |
| Bundled connectors | npm packages deployed in consumer Lambda layer (e.g. @equinox/eq-cart-apps, @equinox/eq-account-apps) |
| MongoDB collections | marketplace_selections, enabled_apps (created automatically by SDK) |
Runtime Invocation
At checkout or other workflows, the consumer adapter:
- Calls
getSelectedApp(instanceId, "USER_PROFILE")(or other capability) - Reads
selection.configurationSchema(notsettings) - Loads the bundled npm connector or calls the external HTTP endpoint
- Passes effective config + caller Bearer token to the connector
Platform Connector Catalog
The platform ships a set of pre-seeded, approved connectors via webbox migration scripts. After seeding, admins must PUT tenant-specific overrides first, then enable per business/store.
| appId | Capability | supportedServices | npm Package |
|---|---|---|---|
eq9-connector-user-iam-v1 |
IAM | user | @equinox/eq-user-apps |
eq9-connector-cart-userprofile-v1 |
USER_PROFILE | cart | @equinox/eq-cart-apps |
eq9-connector-platform-notification-v1 |
NOTIFICATION | order, user, account | @equinox/connectors-notification-v1 |
eq9-connector-cart-catalog-v1 |
CATALOG, PRICING | cart | @equinox/eq-cart-apps |
eq9-connector-platform-inventory-v1 |
INVENTORY | cart, pim | @equinox/eq-cart-apps |
eq9-connector-cart-order-v1 |
ORDER | cart | @equinox/eq-cart-apps |
eq9-connector-platform-payment-v1 |
PAYMENT | cart, user, order | @equinox/eq-cart-apps |
eq9-connector-cart-tax-v1 |
TAX | cart | @equinox/eq-cart-apps |
eq9-connector-cart-address-v1 |
ADDRESS | cart | @equinox/eq-cart-apps |
eq9-connector-cart-shipping-v1 |
SHIPPING | cart | @equinox/eq-cart-apps |
eq9-connector-account-foundation-v1 |
FOUNDATION | account | @equinox/eq-account-apps |
eq9-connector-account-user-v1 |
USER | account | @equinox/eq-account-apps |
Load in shared environments: npm run db:load
Walkthrough: Install a USER_PROFILE Connector
This walkthrough installs a USER_PROFILE connector for cart-service. Replace {MARKETPLACE}, {CART}, {businessId}, {storeId}, {TOKEN}, and {instanceId} with your environment values.
Order: PUT override (Step 5) must be completed before store enable (Step 6).
Step 1 — Create Interface
POST {MARKETPLACE}/v1/interfaces
Authorization: Bearer {TOKEN}
{
"interfaceId": "iface-userprofile-integration-v1",
"name": "UserProfileIntegrationV1",
"version": "1.0",
"category": "userprofile",
"description": "User profile integration - payment profile lookup for saved cards",
"ownerService": "cart-service",
"operations": ["getPaymentProfile"]
}
Step 2 — Create App
POST {MARKETPLACE}/v1/apps
Authorization: Bearer {TOKEN}
Key fields:
{
"appId": "eq-cart-userprofile-connector-v1",
"name": "Cart User Profile Connector",
"version": "1.0.0",
"deployment": { "type": "bundled", "npmPackage": "@equinox/eq-cart-apps" },
"supportedInterfaces": [{ "name": "UserProfileIntegrationV1", "version": "1.0" }],
"capabilities": ["USER_PROFILE"],
"supportedServices": ["cart"],
"authentication": { "type": "bearer-jwt" },
"syncSupported": true,
"asyncSupported": false
}
See the full payload with
endpointsandconfigurationSchemain the testing guide.
Step 3 — Approve App
POST {MARKETPLACE}/v1/apps/eq-cart-userprofile-connector-v1/status
{ "status": "pending", "reason": "Ready for review" }
Then:
{ "status": "approved", "reason": "User profile connector verified", "reviewedBy": "qa" }
Step 4 — Business Enable
POST {MARKETPLACE}/v1/businesses/{businessId}/apps/eq-cart-userprofile-connector-v1/enable
Body: {}
Step 5 — PUT Override with instanceId (triggers selection sync)
PUT {MARKETPLACE}/v1/businesses/{businessId}/stores/{storeId}/apps/eq-cart-userprofile-connector-v1/overrides
{
"layer": "store",
"instanceId": "{instanceId}",
"appliedBy": "admin",
"overrides": {
"configurationSchema": {
"connectorType": "userprofile",
"targetServiceUrl": "https://eq9-dev-env.equinox.shop/userservices-dev",
"hmacSecret": "<secret>",
"storeId": "{storeId}",
"instanceId": "{instanceId}",
"appId": "eq-cart-userprofile-connector-v1",
"timeout": 30000,
"maxRetries": 3
}
}
}
What happens automatically:
- Marketplace publishes
app.selection.syncedto EventBridge - EventBridge delivers to cart-service Lambda
- Cart SDK handler writes to
marketplace_selectionscollection
Step 6 — Store Enable
POST {MARKETPLACE}/v1/stores/{storeId}/apps/eq-cart-userprofile-connector-v1/enable?businessId={businessId}
{
"version": "1.0.0",
"interfaceName": "UserProfileIntegrationV1",
"setAsDefault": true,
"seedDefaults": true
}
Step 7 — Verify in Cart
GET {CART}/v1/marketplace/instances/{instanceId}/apps
Authorization: Bearer {TOKEN}
Expected: 200 OK with a selections array containing adapterCategory: "USER_PROFILE", appSnapshot, and configurationSchema.
Field Mapping: Old Cart Payloads → New Centralized
| Old field (cart selection) | Where it goes | Which API |
|---|---|---|
appId |
appId in Create App |
Step 2 |
adapterCategory: "USER_PROFILE" |
capabilities: ["USER_PROFILE"] |
Step 2 |
configurationSchema.connectorType |
overrides.configurationSchema.connectorType |
Step 5 |
configurationSchema.targetServiceUrl |
overrides.configurationSchema.targetServiceUrl |
Step 5 |
configurationSchema.hmacSecret |
overrides.configurationSchema.hmacSecret |
Step 5 |
configurationSchema.storeId |
overrides.configurationSchema.storeId |
Step 5 |
configurationSchema.instanceId |
Top-level instanceId in override body |
Step 5 |
configurationSchema.timeout |
overrides.configurationSchema.timeout |
Step 5 |
configurationSchema.maxRetries |
overrides.configurationSchema.maxRetries |
Step 5 |
selectedBy |
appliedBy in override body |
Step 5 |
Multi-Service Routing
When one app serves multiple consumer services (e.g. PAYMENT for cart + user + order, or NOTIFICATION for user + order + account), use serviceInstanceMap in the override body instead of a single top-level instanceId:
{
"layer": "store",
"appliedBy": "admin",
"overrides": {
"serviceInstanceMap": {
"cart": "{cartCollectionId}",
"user": "{userCollectionId}",
"order": "{orderCollectionId}"
},
"configurationSchema": {
"targetServiceUrl": "https://...",
"hmacSecret": "<secret>"
}
}
}
Each consumer SDK:
- Filters events where its
serviceIdentityis inapp.supportedServices - Resolves
instanceIdfromserviceInstanceMap[serviceIdentity] - Writes its own row in
marketplace_selections
Full multi-service examples:
CENTRALIZED_MULTI_SERVICE_TESTING_GUIDE.md
Defaults merging:CENTRALIZED_MULTI_SERVICE_WITH_DEFAULTS.md
Marketplace Service Installation
Prerequisites
- Node.js >= 18
- npm >= 9
- MongoDB >= 6.0
- Redis >= 6.0 (optional — caching only)
Quick Start (Local)
# 1. Install dependencies
npm install
# 2. Copy environment file
cp .env.example .env
# 3. Start MongoDB (if not running)
docker run -d -p 27017:27017 mongo:latest
# 4. Verify setup
npm run db:verify
# 5. Seed test data (optional — 5 sample apps + configurations)
npm run db:seed
# 6. Start development server
npm run dev
Authentication (Local)
All API endpoints require a Bearer JWT. For local development, generate a test token when TEST_TOKEN_ALLOWED_ENVS=dev is set in .env. Never enable this in staging or production.
See guides/LOCAL_DEVELOPMENT.md for details.
Key Environment Variables
| Variable | Purpose |
|---|---|
PORT |
Server port (default 5000) |
STAGE |
Environment stage (local, dev, …) |
MONGODB_URI |
MongoDB connection string |
ENABLE_EVENT_BRIDGE |
Set false locally; true in deployed envs |
ENCRYPTION_KEY |
AES-256-GCM for sensitive fields |
JWT_SIGNING_SECRET / Cognito vars |
JWT validation |
CACHE_ENABLED |
Enable/disable Redis cache |
See .env.example for the full list. Detailed guide: guides/LOCAL_DEVELOPMENT.md
Deploy / Seed in Shared Environments
npm run db:load # Load webbox migrations (connector catalog, interfaces)
npm run build
npm start # Production mode
CDK deployment: npm run cdk:deploy (see team deployment process).
Local Dev Without EventBridge
When ENABLE_EVENT_BRIDGE=false (typical local setup), manually push the sync event to the consumer service after Step 5 (override):
POST {CART}/v1/marketplace/events
Content-Type: application/json
Authorization: Bearer {TOKEN}
{
"source": "marketplace-service",
"detailType": "app.selection.synced",
"detail": {
"instanceId": "{instanceId}",
"adapterCategory": "USER_PROFILE",
"appId": "eq-cart-userprofile-connector-v1",
"supportedServices": ["cart"],
"appSnapshot": {
"name": "Cart User Profile Connector",
"version": "1.0.0",
"deployment": { "type": "bundled", "npmPackage": "@equinox/eq-cart-apps" },
"capabilities": ["USER_PROFILE"]
},
"configurationSchema": {
"connectorType": "userprofile",
"targetServiceUrl": "https://eq9-dev-env.equinox.shop/userservices-dev",
"storeId": "{storeId}",
"instanceId": "{instanceId}",
"appId": "eq-cart-userprofile-connector-v1",
"timeout": 30000,
"maxRetries": 3
},
"enabled": true,
"selectedBy": "admin"
}
}
Expected: 200 OK with { "processed": true, "eventId": "..." }
Then verify with GET {CART}/v1/marketplace/instances/{instanceId}/apps.
Business Value
| Capability | Business outcome |
|---|---|
| Central app/connector registry | One authoritative catalogue of every integration; no duplicated or drifting connector metadata across services |
| Approval workflow with lifecycle states | Governance and safety — only approved apps can be enabled; risky changes are gated and audited |
| Per-business / per-store enablement | Multi-tenant flexibility — each merchant/store runs exactly the apps it needs, with a default per interface |
| Layered config overrides | Same connector reused everywhere but tuned per business/store/service without forking the app |
| Versioning + rollback + canary | Safe rollout of new connector versions; instant rollback and gradual (canary) traffic shifting |
| Payment provider configuration & multi-provider routing | Merchants configure payment gateways with primary/secondary failover and weighted routing |
| Notification event/action configuration | Merchants tailor which notifications fire on which events, per channel |
| Health checks + events | Automatic detection of unhealthy external apps and platform-wide event propagation |
| Self-registration + API keys | Apps can register themselves at deploy time (CI/CD or cold start) using machine-to-machine keys |
| Full audit history + point-in-time snapshots | Compliance — who changed what, when, and what the config looked like at any moment |
| Centralized selection sync | Single install path via marketplace; all consumer services stay in sync via app.selection.synced |
Who Uses It
| Persona | How they interact |
|---|---|
| Platform / marketplace admin | Approves apps, enables/disables them per business & store, manages interfaces and config overrides (JWT + admin privileges) |
| App provider / developer | Registers and versions apps, self-registers via API key at deploy time, configures webhooks and mock responses |
| Merchant / store operator | Configures payment providers, notification events & actions for their store (via admin portal → JWT) |
| Consuming platform services (cart, PIM, OMS, subscription, user, account) | Subscribe to app.* EventBridge events; read effective config to discover and invoke enabled apps |
| CI/CD pipeline | Emits app.deployed EventBridge events (source: custom.pipeline) that trigger self-registration |
| Auditors | Query app/enablement change history, compare and reconstruct point-in-time state (marketplace:auditor) |
Core Features
| # | Feature | Description |
|---|---|---|
| 1 | App registry (CRUD) | Create, read, update, soft-delete apps with rich metadata, deployment, interfaces, capabilities and auth config |
| 2 | Approval workflow | Lifecycle states draft → pending → approved → deprecated → archived (+ rejected, deleted) with a validated transition map |
| 3 | Quick create | One-shot app creation with optional auto-approve and deployment validation (npm view, Lambda dry-run, HTTP health check) |
| 4 | Interface definitions | Register named/versioned interfaces, validate apps against them, list apps per interface, produce migration reports |
| 5 | Store-level enablement | Enable/disable apps per store per interface, set defaults, rollback, drain (graceful wind-down) |
| 6 | Business-level enablement | Enable/disable apps at the business tier (prerequisite for some flows, e.g. PAYMENT) |
| 7 | Layered config overrides | Business / store / service override layers deep-merged into an effective config, incl. serviceInstanceMap |
| 8 | Payment configuration | Per-store payment provider config (encrypted secrets) + multi-provider routing (primary/secondary, weighted, failover) |
| 9 | Notification configuration | Per-store notification events, actions (EMAIL/SMS/PUSH), restricted-event suppression, readiness checks |
| 10 | Webhooks | Register external subscribers, deliver app.* notifications, view delivery history and audit logs |
| 11 | Mock responses | Store and test mock responses embedded in an app for integration testing |
| 12 | Routing / hooks / canary | Routing strategies, pre/post hooks, canary rollout with auto-promote/rollback |
| 13 | Self-registration & API keys | Machine-to-machine app self-registration from an app manifest + API-key lifecycle (max 5 active) |
| 14 | Health checks | Periodic health probing of external apps with healthy/degraded/unhealthy state + events |
| 15 | Eventing | Publishes app.* / business.app.* EventBridge events; consumes app.deployed and interface/deprecation events |
| 16 | Centralized selection sync | PUT override with instanceId / serviceInstanceMap publishes app.selection.synced to populate consumer marketplace_selections |
Deep-Dive Guides Index
| Topic | Guide |
|---|---|
| Multi-service routing + defaults | CENTRALIZED_MULTI_SERVICE_TESTING_GUIDE.md |
| ConfigurationSchema defaults | CENTRALIZED_MULTI_SERVICE_WITH_DEFAULTS.md |
| PIM / Inventory / Order (cart) | CENTRALIZED_PIM_INVENTORY_ORDER_APPS.md |
| Account foundation + user | UNIFIED_ACCOUNT_FOUNDATION_USER_CONNECTOR_PAYLOADS.md |
| Unified notification connector | UNIFIED_NOTIFICATION_CONNECTOR_PAYLOADS.md |
| App sync architecture | apps/APP_MANAGEMENT_AND_SYNC.md |
| Centralized selection design | CENTRALIZED_APP_SELECTION_PLAN.md |
| Local development | guides/LOCAL_DEVELOPMENT.md |
| Quick reference (patterns) | guides/quick-reference-guide.md |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Selection missing in consumer after override | EventBridge disabled locally, wrong serviceIdentity, or service not in supportedServices |
Check ENABLE_EVENT_BRIDGE; verify plugin serviceIdentity; confirm override had instanceId or serviceInstanceMap; use manual event POST locally |
| Wrong config at runtime | Connector reading deprecated settings instead of configurationSchema |
Verify selection document has configurationSchema; upgrade connector/SDK |
| Store enable fails | Override not applied before store enable | PUT override with required config before store enable |
instanceId mismatch |
Used storeId instead of service collectionId | Re-fetch from GET {FOUNDATION}/v1/stores/{storeId}/effective |
| Multi-service app only synced to one service | Missing or incorrect serviceInstanceMap key |
Ensure map keys match serviceIdentity values (cart, user, account, …) |
| Override validation fails | Required schema fields missing | Add required fields or define default values in app configurationSchema at registration |
Related Resources
| Resource | Location |
|---|---|
| Admin portal | Front end consuming the API (apps, enablement, config overrides, contracts, provisioning, package management) |
| Consumer SDK | eq-component/eq-marketplace-integration — event handler and plugin used by other services |
| Shared connector packages | @equinox/eq-cart-apps, @equinox/eq-account-apps, @equinox/eq-user-apps, @equinox/connectors-notification-v1 |
| Contracts / schemas | Capability contracts and the app manifest schema (docs/contracts/) |
| Event fan-out rule | AWS EventBridge rule that fans app.* events out to consumer service Lambdas |
| Webbox seed data | webbox/v1.0.0/ — connector catalog and interface definitions |
| Service README | README.md |
Revision History
| Date | Author | Change |
|---|---|---|
| 2026-08-05 | JP | Created the Confluence page (marketplace-doc.txt) |
| 2026-08-12 | Platform Engineering | Enhanced with architecture diagrams, centralized selection model, installation steps, consumer sync mechanics, platform connector catalog, and USER_PROFILE walkthrough |
| 2026-08-12 | Platform Engineering | Replaced Mermaid diagrams with SVG images for Confluence/markdown compatibility |
Revision History
2026-08-12 | JP – Created the page and added the content.