The Brand to Agency solution is an active proof of concept being developed using Adobe App Builder. It's designed to connect asset workflows between brand-owned AEM environments and agencies in a secure and auditable way—enabling brands to share assets with agencies without requiring direct access to the brand's systems. This POC establishes a repeatable pattern that can be shared with brands to build their own Brand-to-Agency extension using Adobe App Builder and distributing on Adobe Exchange.
- Node.js = 20
- Adobe I/O CLI (
aio) - Adobe Developer Console access
- Clone the repository
- Install dependencies:
npm install
- Populate the
.envfile in the project root and fill it as shown below - Start the development server:
aio app run -e dx/excshell/1
Demo mode is automatically enabled for development and provides a fully functional UI with mock data.
aio app run -e dx/excshell/1- âś… 3 mock company registrations with different statuses
- âś… Functional registration form with simulated API responses
- âś… Full CRUD operations (create, read, update, delete)
- âś… Search and filtering capabilities
- âś… Realistic 1-2 second delays for API simulation
- Blue header with "Brand/Client (Demo Mode)" label
- "Demo Mode Active" status indicator in header
- Header navigation (replaced sidebar)
- Console messages prefixed with "[DEMO MODE]"
đź“– Complete Demo Mode Documentation
aio app run -e dx/excshell/1- Run the application (recommended)npm run run:application- Run the application locallynpm run run:excshell- Run the application in Experience Cloud Shell
This project uses several key Adobe and React libraries:
- @adobe/aio-sdk
- @adobe/exc-app
- @adobe/react-spectrum
- React 16.13.1
- TypeScript
- Jest for testing
You can generate the .env file using the command aio app use. After generation, you'll need to update several variables with your specific Adobe Developer Console and AEM Cloud Service settings.
# This file must **not** be committed to source control
# Adobe I/O Runtime
AIO_RUNTIME_AUTH=
AIO_RUNTIME_NAMESPACE=
# Adobe I/O Events
AIO_AGENCY_EVENTS_REGISTRATION_PROVIDER_ID=
AIO_AGENCY_EVENTS_AEM_ASSET_SYNC_PROVIDER_ID=
# AEM Authentication
AEM_AUTH_CLIENT_SECRET=
AEM_AUTH_SCOPES=
AEM_AUTH_CLIENT_ID=
AEM_AUTH_TECH_ACCOUNT_ID=
AEM_AUTH_PRIVATE_KEY=
AEM_AUTH_TYPE=
# Service-to-Service Authentication
S2S_API_KEY=
S2S_CLIENT_SECRET=
S2S_SCOPES=
# Organization
ORG_ID=
# Application Configuration
AIO_APP_NAME=
AGENCY_BASE_URL=
# AEM Cloud Service Configuration
AIO_ASSET_SYNCH_TARGET_PROGRAM_ENVIRONMENT=1. Adobe Developer Console Settings
Update these variables with values from your Adobe Developer Console project:
ORG_ID- Your Adobe organization IDS2S_API_KEY- Service-to-service API keyS2S_CLIENT_ID- Service-to-service client IDS2S_CLIENT_SECRET- Service-to-service client secret
2. Application-Specific Settings
Update these variables to your own target values:
AIO_APP_NAME- Your application name (e.g., "a2b-brand")AGENCY_BASE_URL- Base domain URL for the agency application- Format:
https://{namespace}.{domain}(without any paths) - Example:
https://27200-a2b-benge.adobeio-static.net - Important: Do NOT include any paths like
/api/v1/web/a2b-agency - The application will construct the full endpoint URL:
{AGENCY_BASE_URL}/api/v1/web/a2b-agency/new-brand-registration
- Format:
3. AEM Cloud Service Configuration
These values must come from your AEM Cloud Service Developer Console:
AEM_AUTH_CLIENT_ID- AEM authentication client IDAEM_AUTH_TECH_ACCOUNT_ID- AEM technical account IDAEM_AUTH_CLIENT_SECRET- AEM authentication client secretAEM_AUTH_PRIVATE_KEY- AEM private key (see special formatting below)AIO_ASSET_SYNCH_TARGET_PROGRAM_ENVIRONMENT- Target program environment
Important: AEM Private Key Formatting
The AEM_AUTH_PRIVATE_KEY requires special formatting:
- Take the JWT token from AEM Developer Console
- Remove the last
\r\n - Replace all other
\r\nwith\n - Surround the entire key with double quotes
""
4. Event Provider Configuration
You need to configure event providers for asset synchronization and brand registration:
# List existing event providers
aio event provider listLook for providers with these labels:
- Asset sync events: "A2B Asset Sync" or similar
- Brand registration: "a2b-Brand Registration" or similar
Update these variables with the UUIDs from your providers:
AIO_AGENCY_EVENTS_AEM_ASSET_SYNC_PROVIDER_ID- UUID for asset sync event providerAIO_AGENCY_EVENTS_REGISTRATION_PROVIDER_ID- UUID for brand registration event provider
If no providers exist, create them:
# Create asset sync event provider
aio event provider create
# Label: "A2B Asset Sync"
# Description: "Event from Agency to Brand about an asset update"
# Create brand registration event provider
aio event provider create
# Label: "a2b-Brand Registration"
# Description: "Events from an agency into a brand"After creating providers, run aio event provider list again to get the UUIDs and update your .env file.
The application implements a sophisticated runtime isolation mechanism to prevent event cross-contamination between different development environments. This is particularly important when multiple developers are working simultaneously or debugging in the same IMS organization, as event providers are shared across the entire organization.
-
Runtime Parameter Injection: Every action receives an
APPLICATION_RUNTIME_INFOparameter containing:{ "namespace": "${AIO_runtime_namespace}", "app_name": "${AIO_APP_NAME}" } -
Namespace Parsing: The namespace is automatically parsed into three distinct components:
consoleId: The Adobe Developer Console identifierprojectName: The specific project nameworkspace: The development workspace (e.g., dev, stage, prod)
-
Event Enrichment: Every event published through the EventManager automatically includes an
app_runtime_infoproperty in the event data:{ "app_runtime_info": { "consoleId": "console123", "projectName": "a2b-brand", "workspace": "dev" } }
- Environment Isolation: Events can be filtered by runtime information, ensuring you only process events from your specific development environment
- Debugging Clarity: Eliminates confusion from seeing events from other developers' environments
- Multi-tenant Safety: Prevents accidental processing of events from other projects or workspaces
- Audit Trail: Provides clear traceability of which environment generated each event
The runtime information is automatically handled by the EventManager and IoCustomEventManager classes. No additional code is required in your event handlers - the system transparently adds the runtime context to all outgoing events.
This feature is particularly valuable in enterprise development scenarios where multiple teams may be working on similar applications within the same Adobe I/O organization, ensuring clean separation of concerns and preventing unintended event processing.
benge app project in TMD dev org: 27200-brand2agency-stage
title: brand to agency.
├── src/ # Source code
│ ├── actions/ # Adobe I/O Runtime actions
│ │ ├── agency-assetsync-event-handler/ # Asset sync event handling
│ │ ├── agency-assetsync-internal-handler/ # Internal assetsync event handler
│ │ ├── agency-event-handler/ # Agency event routing
│ │ ├── adobe-product-event-handler/ # Adobe product event handling
│ │ ├── classes/ # Shared classes
│ │ ├── types/ # TypeScript type definitions
│ │ ├── utils/ # Utility functions
│ │ └── constants.ts # Shared constants
│ └── dx-excshell-1/ # Experience Cloud Shell configuration
│ ├── web-src/ # Web application source
│ ├── test/ # Unit tests
│ ├── e2e/ # End-to-end tests
│ └── ext.config.yaml # Extension configuration
├── test/ # Test files
├── docs/ # Documentation
├── setup/ # Setup scripts
└── dist/ # Build output
The application exposes the following endpoints:
POST /api/v1/web/a2b-brand/agency-event-handler- Route agency events to appropriate internal handlers- Required Parameters:
APPLICATION_RUNTIME_INFO(JSON string with namespace and app_name),type(event type),data(event data with app_runtime_info) - Validation: Ensures event workspace matches action workspace for environment isolation
- Event Types Handled:
com.adobe.a2b.assetsync.*→ routes toagency-assetsync-internal-handler
- Required Parameters:
POST /api/v1/web/a2b-brand/adobe-product-event-handler- Route events from Adobe products to appropriate internal handlers- Required Parameters passed in config:
APPLICATION_RUNTIME_INFO(JSON string with namespace and app_name) - Event Types Handled:
aem.assets.asset.created→ routes toagency-assetsync-event-handleraem.assets.asset.updated→ routes toagency-assetsync-event-handleraem.assets.asset.deleted→ routes toagency-assetsync-event-handleraem.assets.asset.metadata_updated→ routes toagency-assetsync-event-handler
- Required Parameters passed in config:
The application also includes the following non-web actions:
agency-assetsync-event-handler- Handle asset synchronization events from agencies (direct action invocation only)- Required Parameters:
APPLICATION_RUNTIME_INFO(JSON string with namespace and app_name)
- Required Parameters:
agency-assetsync-internal-handler- Internal handler for com.adobe.a2b.assetsync events (direct action invocation only)- Required Parameters:
APPLICATION_RUNTIME_INFO(JSON string with namespace and app_name),type(event type),data(event data with app_runtime_info)
- Required Parameters:
All actions and endpoints automatically include runtime information in all published events for environment isolation.
The application implements a two-tier event routing system:
The adobe-product-event-handler serves as a central router for Adobe product events. It receives events from various Adobe products and routes them to the appropriate internal handlers based on the event type.
The agency-event-handler serves as a central router for agency events. It receives events from agencies and routes them to the appropriate internal handlers based on the event type.
The agency event handler includes workspace validation to ensure environment isolation:
- Validation Logic: Compares
params.data.app_runtime_info.workspacewith the workspace parsed fromAPPLICATION_RUNTIME_INFO - Security: Prevents events from different workspaces (dev, stage, prod) from being processed by the wrong environment
- Error Handling: Returns 400 error with clear message when workspace mismatch is detected
- Event Reception: The
adobe-product-event-handlerreceives events from Adobe products via webhook - Event Type Analysis: The handler examines the
typefield of the incoming event - Routing Decision: Based on the event type, it determines which internal handler should process the event
- Action Invocation: Uses OpenWhisk action invocation to call the appropriate internal handler
- Result Return: Returns the routing result along with the original event processing status
| Adobe Product Event Type | Internal Handler | Description |
|---|---|---|
aem.assets.asset.created |
agency-assetsync-event-handler |
AEM asset creation events |
aem.assets.asset.updated |
agency-assetsync-event-handler |
AEM asset update events |
aem.assets.asset.deleted |
agency-assetsync-event-handler |
AEM asset deletion events |
aem.assets.asset.metadata_updated |
agency-assetsync-event-handler |
AEM asset metadata changes |
| Agency Event Type | Internal Handler | Description |
|---|---|---|
com.adobe.a2b.assetsync.new |
agency-assetsync-internal-handler |
New asset synchronization events |
com.adobe.a2b.assetsync.updated |
agency-assetsync-internal-handler |
Asset synchronization update events |
com.adobe.a2b.assetsync.deleted |
agency-assetsync-internal-handler |
Asset synchronization deletion events |
To add support for new Adobe product events:
- Add the new event type to the switch statement in
adobe-product-event-handler - Create a new routing function (e.g.,
routeToCreativeCloudHandler) - Create the corresponding internal handler action
- Update the documentation and tests
For more information, visit the Unified Shell API documentation.
- Create event provider:
aio event provider createLabel: "a2b-Brand Registration" Description: Events from an agency into a brand
- Create event metadata:
aio event eventmetadata create <id>- Brand Registration Received
- Label: "Brand Registration Received"
- Code:
com.adobe.a2b.registration.received - Description: "This contains an echo of event that was received from remote brand"
{
"specversion": "1.0",
"id": "20daaf84-c938-48e6-815c-3d3dfcf8c900",
"source": "urn:uuid:fefcd900-66b6-4a46-9494-1b9ff1c5d0ac",
"type": "com.adobe.a2b.registration.received",
"datacontenttype": "application/json",
"time": "2025-06-08T05:44:51.686Z",
"eventid": "591c4e47-6ba1-4599-a136-5ccb43157353",
"event_id": "591c4e47-6ba1-4599-a136-5ccb43157353",
"recipient_client_id": "4ab33463139e4f96b851589286cd46e4",
"recipientclientid": "4ab33463139e4f96b851589286cd46e4",
"data": {
"brandId": "2e59b727-4f9c-4653-a6b9-a49a602ec983",
"secret": "PFVZNkBLH9iquYvr8hGSctesInK4QlRh",
"name": "test agency benge 37",
"endPointUrl": "https://pathtoendpoint/37",
"enabled": false,
"createdAt": "2025-06-08T05:44:51.219Z",
"updatedAt": "2025-06-08T05:44:51.219Z",
"app_runtime_info": {
"consoleId": "console123",
"projectName": "a2b-brand",
"workspace": "dev"
}
}
}- Agency Registration Enabled
- Label: "Agency Registration Enabled"
- Code:
com.adobe.a2b.registration.enabled - Description: "When an admin approves a brand registration this event is thrown"
{
"specversion": "1.0",
"id": "381691a0-a5c6-4c97-b1ac-662a06686856",
"source": "urn:uuid:fefcd900-66b6-4a46-9494-1b9ff1c5d0ac",
"type": "com.adobe.a2b.registration.enabled",
"datacontenttype": "application/json",
"time": "2025-06-08T05:39:47.227Z",
"eventid": "d72bccdb-1af0-4c01-b802-fea422383017",
"event_id": "d72bccdb-1af0-4c01-b802-fea422383017",
"recipient_client_id": "4ab33463139e4f96b851589286cd46e4",
"recipientclientid": "4ab33463139e4f96b851589286cd46e4",
"data": {
"aid": "f94496b9-a40c-4d7a-8c4e-e59db029f247",
"secret": "Uebq3tGYkoDoxonUxQizqKFHzHG703F1",
"name": "test agency benge 36",
"endPointUrl": "https://pathtoendpoint/36",
"enabled": false,
"createdAt": "2025-06-08T05:39:46.778Z",
"updatedAt": "2025-06-08T05:39:46.778Z",
"app_runtime_info": {
"consoleId": "console123",
"projectName": "a2b-brand",
"workspace": "dev"
}
}
}- Brand Registration Disabled
- Label: "Brand Registration Disabled"
- Code:
com.adobe.a2b.registration.disabled - Description: "When an admin disables a brand registration this event is thrown"
{
"specversion": "1.0",
"id": "706c19f6-2975-49a3-9e33-39672aed756e",
"source": "urn:uuid:fefcd900-66b6-4a46-9494-1b9ff1c5d0ac",
"type": "com.adobe.a2b.registration.disabled",
"datacontenttype": "application/json",
"time": "2025-06-08T05:42:22.333Z",
"eventid": "175cf397-6b9f-4bb9-9aaa-943d5c42333d",
"event_id": "175cf397-6b9f-4bb9-9aaa-943d5c42333d",
"recipient_client_id": "4ab33463139e4f96b851589286cd46e4",
"recipientclientid": "4ab33463139e4f96b851589286cd46e4",
"data": {
"bid": "4e9976ab-95ea-47c1-a2e3-7e266aa47935",
"secret": "OMWwg3qNE5Mxlwye1KGXj3zYy7ORT9FC",
"name": "test client benge 36",
"endPointUrl": "https://pathtoendpoint/36",
"enabled": false,
"createdAt": "2025-06-08T05:42:21.855Z",
"updatedAt": "2025-06-08T05:42:21.855Z",
"app_runtime_info": {
"consoleId": "console123",
"projectName": "a2b-brand",
"workspace": "dev"
}
}
}Update your .env with the provider id returned by aio event provider create:
AIO_AGENCY_EVENTS_REGISTRATION_PROVIDER_ID=fefcd900-fake-fake-fake-1b9ff1c5d0acaio event provider createLabel: "A2B Asset Sync" Description: Event from Agency to Brand about an asset update
Create event metadata:
aio event eventmetadata create <provider_id>-
New Asset Published
- Label: "New Agency Asset Published"
- Code:
com.adobe.a2b.assetsync.new - Description: "Asset that has never been synced before is coming over for the first time"
- Event body: Includes
app_runtime_infofor environment isolation
-
Asset Updated
- Label: "Asset Updated"
- Code:
com.adobe.a2b.assetsync.updated - Description: "Asset that has been synced before has changed"
- Event body: Includes
app_runtime_infofor environment isolation
-
Asset Deleted
- Label: "Asset Deleted"
- Code:
com.adobe.a2b.assetsync.deleted - Description: "Asset that has been synced before has been deleted"
- Event body: Includes
app_runtime_infofor environment isolation
The agency-assetsync-event-handler is a non-web action that can be invoked directly by other actions or services. It handles asset synchronization events and can be triggered programmatically.
For AEM asset event processing, you can:
- Invoke the action directly from other actions using the Adobe I/O Runtime SDK
- Set up triggers to automatically invoke the action based on specific conditions
- Use the action as part of a workflow or pipeline
Events that can be processed:
- Asset deleted event:
aem.assets.asset.deleted - Asset metadata updated event:
aem.assets.asset.metadata_updated
These events will be published to the BRAND and also echoed locally for secondary in-house systems use.
removing all your runtime actions
aio rt actions list --json | jq -r '.[] | (.namespace + "/" + .name)' | while read -r a; do [ -n "$a" ] && aio rt action delete "$a"; done
-
Authentication Issues
- Ensure all required environment variables are set
- Verify Adobe I/O credentials are valid
- Check AEM authentication configuration
-
Asset Synchronization Issues
- Verify AEM event subscriptions
- Check asset sync provider configuration
- Review logs for detailed error messages
- all event are cloud events see (cloud events)[https://github.com/cloudevents/spec]
- Ensure you have the required dependencies installed
- Follow the coding standards (ESLint configuration is provided)
- Write tests for new features that are actions. UI is nice to have but not wired
- Submit a pull request