# Access via JWT Source: https://docs.withampersand.com/api/jwt-auth This guide explains how to generate and use JWTs for authentication, so that you can securely interact with Ampersand APIs from your frontend and your customers will only have access to their own Ampersand installations. When you first start using Ampersand's [prebuilt UI components](/embeddable-ui-components) or [headless UI](/headless), using a frontend API key is convenient since it does not require you to implement any server logic. However, as you move into production, we recommend that you stop using frontend API keys and start using JWT authentication for better security. Do not do JWT signing in the frontend. It invalidates the security model. ## Overview The overall flow uses RSA-based JWT tokens with RS256 signing. It consists of: 1. **RSA Key Pair Generation** - Create a public/private key pair. 2. **Key Registration** - Register the public key with Ampersand. 3. **JWT Token Generation** - Create signed JWT tokens in your server. 4. **API Authentication** - Use JWTs to authenticate API requests made by your frontend code, by providing a `getToken` method to the Ampersand UI library. ## 1. Generate RSA key pair First, generate an RSA key pair for signing JWTs. You can use any crypto library that can generate an RSA key pair. Only RS256 algorithm is supported. The example below uses the Node.js library `crypto`. You only need to do this step when you first implement JWT auth, and every time you want to rotate keys. ```javascript theme={null} const crypto = require('crypto'); const fs = require('fs'); // Generate RSA key pair (2048-bit recommended) const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs1', format: 'pem' } }); // Persist the keys somewhere fs.writeFileSync('private_key.pem', privateKey); fs.writeFileSync('public_key.pem', publicKey); console.log('RSA key pair generated successfully!'); ``` ## 2. Register public key with Ampersand ### Using the Ampersand Dashboard You can add your RSA public key and view your existing Key IDs (kid) directly in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/jwt-keys). JWT Key Management in Dashboard ### Using the API You can also register the public key programmatically using the [Create a new JWT key endpoint](/reference/jwt-key/create-a-new-jwt-key). ## 3. Generate JWT tokens in server Create JWT tokens for a particular `groupRef` and `consumerRef` combo using public and private keys generated in step 1. You can use any library of choice that can sign JWT keys. The example below uses the Node.js library `jsonwebtoken`. You need to then expose an API endpoint that your frontend code can call to fetch a JWT token. ```javascript theme={null} const jwt = require('jsonwebtoken'); const fs = require('fs'); // Function for generating JWT function generateJWT(keyId, claims, ttlSeconds = 600) { const privateKey = fs.readFileSync('private_key.pem', 'utf8'); // JWT payload with required claims const payload = { sub: claims.subject, groupRef: claims.groupRef, consumerRef: claims.consumerRef, iat: Math.floor(Date.now() / 1000), // Issued at time // Expiry time, the max TTL allowed is 600 seconds. exp: Math.floor(Date.now() / 1000) + ttlSeconds }; // Sign the token const token = jwt.sign(payload, privateKey, { algorithm: 'RS256', header: { kid: keyId // Key ID returned by Ampersand in step 2. } }); return token; } // Expose an API that your frontend code can call. // This example assumes you are using Express. // You need to adapt this code to your server framework. const app = express(); app.use(express.json()); app.post('/api/generate-jwt', async (req, res) => { try { const { groupRef, consumerRef } = req.body; const token = generateJWT('your-key-id', { groupRef: groupRef, consumerRef: consumerRef, // The subject can be any string that helps you with debugging, // such as username, email, etc. Ampersand doesn't pay attention // to this field. subject: 'user@example.com', }, 300); // 5 minute expiration res.json(token); } catch (error) { res.status(500).json({ error: 'Internal server error', message: 'Failed to generate JWT token' }); } }); ``` ## 4. Fetch JWT token in frontend This functionality is only available in `@amp-labs/react` version `2.8.7` and above. In [AmpersandProvider](/embeddable-ui-components#credentials), instead of using the `apiKey` property, provide a `getToken` method that calls your backend to fetch a JWT token. The UI library will call it whenever it needs to generate a new token, or when the existing token expires. The `getToken` method must have the following type signature: `(params: {consumerRef: string, groupRef: string}) => Promise` ```javascript theme={null} import { AmpersandProvider } from '@amp-labs/react'; import '@amp-labs/react/styles'; const options = { project: 'my-ampersand-project', getToken: ({consumerRef, groupRef}) => { const token = await getTokenFromMyBackend(consumerRef, groupRef); return token; } }; function App() { return ( // You can use any of the Ampersand components here. ) } ``` ## Key management API endpoints The backend provides these endpoints for managing JWT keys: * [Register a new public key](/reference/jwt-key/create-a-new-jwt-key) * [List all keys for a project](/reference/jwt-key/list-jwt-keys) * [Get a specific key](/reference/jwt-key/get-a-specific-jwt-key) * [Update a key's label or status](/reference/jwt-key/update-a-jwt-key) * [Delete a key](/reference/jwt-key/delete-a-jwt-key) ## Important security notes 1. **Private Key Security**: Never expose private keys in client-side code. Store them securely on your backend servers. 2. **Token Expiration**: Tokens can have a maximum TTL of 10 minutes (600 seconds). The Ampersand UI library will automatically call the `getToken` function you provide when the token expires. 3. **Key Rotation**: Regularly rotate your RSA key pairs for better security. # Access via API key Source: https://docs.withampersand.com/api/key-auth ## Using the API All API endpoints require authenticated access via an API key passed as a header `X-Api-Key`: your Ampersand API key. If you don't have one yet, create as many as you'd like on the Ampersand Dashboard's [API Keys page](https://dashboard.withampersand.com/projects/_/api-keys). ## Creating and Viewing API Key Generating an API key 1. Go to the [API Keys page](https://dashboard.withampersand.com/projects/_/api-keys) on the Ampersand Dashboard. 2. Click on the "New API Key" button. 3. Give your API key a descriptive name and click on the "Save Changes" button. 4. Your API key will be generated. You can view it by clicking on the button with the name of the key. Copy API key # Overview Source: https://docs.withampersand.com/api/overview Welcome to the Ampersand API reference docs! If you are new to Ampersand, this is not the best place to get started. Depending on your use case, you may not actually need to use the API. Please check out our [Quickstart guide](/quickstart) instead. Ampersand offers 4 APIs: * **Write API**: for writing data to your customer's SaaS instance. Learn more in [Write Actions](/write-actions). * **Read API**: for advanced read use cases like triggering an off-schedule read of your customer's SaaS instance, or for managing read schedules. If you simply want to do scheduled reads, you do not need to use this API. Please refer to [Read Actions](/read-actions). * **Platform API**: for advanced use cases in managing integrations and customer installations. Most customers do not use the Platform API directly, but interact with the Ampersand Dashboard, CLI or UI library instead. * **Proxy API**: for making direct API requests to your customer's SaaS instance without any transformations. Learn more in [Proxy Actions](/proxy-actions). > If you prefer exploring APIs interactively, check out the official Ampersand Postman Collection. > [Run In Postman](https://god.gw.postman.com/run-collection/29973051-00c27855-f4bf-48be-8730-a63a19aada9b?action=collection%2Ffork\&source=rip_markdown\&collection-url=entityId%3D29973051-00c27855-f4bf-48be-8730-a63a19aada9b%26entityType%3Dcollection%26workspaceId%3D4646d536-d0c0-45a7-a2ad-058b0eae9c2b#?env%5BProduction%5D=W3sia2V5IjoiYmFzZVVybCIsInZhbHVlIjoiaHR0cHM6Ly9hcGkud2l0aGFtcGVyc2FuZC5jb20vdjEiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoicmVhZEJhc2VVcmwiLCJ2YWx1ZSI6Imh0dHBzOi8vcmVhZC53aXRoYW1wZXJzYW5kLmNvbS92MSIsInR5cGUiOiJkZWZhdWx0IiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJ3cml0ZUJhc2VVcmwiLCJ2YWx1ZSI6Imh0dHBzOi8vd3JpdGUud2l0aGFtcGVyc2FuZC5jb20vdjEiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiYXBpS2V5IiwidmFsdWUiOiIiLCJ0eXBlIjoic2VjcmV0IiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJwcm9qZWN0SWQiLCJ2YWx1ZSI6IiIsInR5cGUiOiJkZWZhdWx0IiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJpbnRlZ3JhdGlvbklkIiwidmFsdWUiOiIiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoicHJvdmlkZXJBcHBJZCIsInZhbHVlIjoiIiwidHlwZSI6ImRlZmF1bHQiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6Imluc3RhbGxhdGlvbklkIiwidmFsdWUiOiIiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5Ijoib2JqZWN0TmFtZSIsInZhbHVlIjoiIiwidHlwZSI6ImRlZmF1bHQiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6Imdyb3VwUmVmIiwidmFsdWUiOiIiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9XQ==) # Overview Source: https://docs.withampersand.com/cli/overview The Ampersand Command Line Interface (CLI) enables you to deploy integrations and interact with the Ampersand platform. ## Installation ### For macOS using Homebrew To install the CLI using Homebrew (macOS users): ```bash theme={null} brew tap amp-labs/cli brew update brew install amp-labs/cli/cli ``` #### Updating to new version If you already have the CLI installed, and want to upgrade to the latest version, run: ``` brew upgrade amp-labs/cli/cli ``` ### For Windows 1. Visit [Ampersand CLI Releases](https://github.com/amp-labs/cli/releases). 2. Download the appropriate binary for your machine’s architecture. 3. Extract the downloaded folder containing the `exe` file. 4. To use the CLI, open Command Prompt in the extracted folder: * Navigate to the folder in File Explorer * Click in the address bar * Type `cmd` and press Enter 5. Verify the installation by running: ```cmd theme={null} amp ``` This should display a list of commands or options available in the CLI, confirming that it is properly configured and ready for use. 6. To make the CLI accessible from anywhere, add the folder to your `PATH`: * Open the Start menu * Search for "Environment Variables" * Click "Edit the system environment variables" * Click "Environment Variables" * Under "System variables", select "Path" and click "Edit" * Click "New" and add the path to the folder containing the `exe` file * Click "OK" on all windows ### For other systems 1. Visit [Ampersand CLI Releases](https://github.com/amp-labs/cli/releases). 2. Download the appropriate binary for your machine’s architecture. 3. Place the binary somewhere on your `PATH` (e.g. `/usr/bin`). **Note:**\ To ensure the CLI is accessible from anywhere, the binary must be placed in a directory listed in your `PATH`. You can inspect your current `PATH` using the following commands: * **Linux/macOS:** ```bash theme={null} echo $PATH ``` ## Using the CLI ### Checking the version To check the version and build information of your CLI, run: ``` amp version ``` ### Log into your Ampersand account Ensure you have an Ampersand account. Create one on the [Ampersand Dashboard](https://dashboard.withampersand.com/sign-up) if you don't already have one. To log into the CLI: ``` amp login ``` To log out: ``` amp logout ``` ### Deploy integrations Once you have defined your integrations in an `amp.yaml` file, use the following commands to deploy: ```bash theme={null} amp login amp deploy --project ``` ### View all commands To list all available commands and global flags: ```bash theme={null} amp help ``` **Note:** For detailed explanations of each command and its options, refer to the [Command Reference](/cli/reference) section. ### Running in CI/CD Environments #### Github Action You can use the [official Ampersand Github Action](https://github.com/marketplace/actions/official-ampersand-github-action) to automatically deploy integrations from your repository. #### Generate an API key If you are using the official Github Action, or if you want to create your own CI/CD pipeline, you will need to generate an API key to run the CLI in a non-interactive mode that does not require a log in prompt. 1. Create an API key in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/api-keys). 2. Use the API key with the `key` flag. ``` amp list:integrations --key --project ``` Alternatively, you can set the API key as an environment variable: ``` export AMP_API_KEY= amp list:integrations --project ``` ### Debug mode For more detailed output, run commands with the `debug` flag. ``` amp login --debug ``` # Reference Source: https://docs.withampersand.com/cli/reference This section provides a detailed overview of the Ampersand CLI, including the available commands, their usage, and options. ## Usage The general syntax for using the Ampersand CLI is: ``` amp [command] [flags] ``` Where `[command]` is one of the available commands, and `[flags]` are parameters that modify the command's behavior. ## Global flags Global flags are options available for any Ampersand CLI command, allowing you to modify behavior or provide additional information consistently across all operations. Here are the available global flags: * `-d, --debug`: Enables debug logging mode (default: false). This flag is useful for troubleshooting as it provides more detailed output about the command's execution. * `-h, --help`: Displays help information for the command. This can be used to get more details about a specific command's usage and options. * `-k, --key string`: Specifies the Ampersand API key. This allows you to authenticate your CLI sessions using an API key instead of interactive login, which can be particularly useful in automated scripts or CI/CD pipelines. * `-p, --project string`: Specifies the Ampersand project name or ID. You can use `amp list:projects` to view available projects. You can use these flags by adding them after any command. For example: ``` amp list:integrations --debug --project my-project ``` This command would list integrations for the project "my-project" with debug logging enabled. ## Authentication commands These commands allow you to manage your session with the Ampersand CLI. Use these to log in to your Ampersand account before running other commands. As an alternative to logging in, you can also run commands using an API key. See [Running in CI/CD environments](/cli/overview#running-in-ci-cd-environments) for details. ### login Authenticates you against the CLI via an interactive browser flow. **Syntax:** ```bash theme={null} amp login ``` **Example:** ```bash theme={null} amp login ``` ### logout Logs out from your Ampersand account. **Syntax:** ```bash theme={null} amp logout ``` ## Utility commands These commands provide general functionality to help you use and understand the Ampersand CLI. ### help Displays help information about any command. You can use `amp help` for general help and `amp help [command]` for command-specific help. **Syntax:** ```bash theme={null} amp help [command] ``` **Example:** ```bash theme={null} amp help deploy ``` **Notes:** * Provides detailed information about command usage and available options. ### version Displays the version of the Ampersand CLI installed on your system. **Syntax:** ```bash theme={null} amp version ``` **Notes:** * Useful for verifying your CLI version or checking if updates are available. * Outputs the current version number of the CLI. **Example:** ```bash theme={null} amp version ``` ## Managing integrations These commands are used to manage your integrations within Ampersand. They allow you to deploy changes to your integrations, delete integrations, and manage installations of integrations. ### deploy Deploys the integrations defined in the `amp.yaml` file to Ampersand. If an integration already exists, this command will apply any changes to the existing integration. **Syntax:** ```bash theme={null} amp deploy --project [--key ] [--debug] ``` **Notes:** * Requires a folder containing an `amp.yaml` file. * Use this command to deploy new integrations or update existing ones. * Use this command when you've made changes to your integration **Example:** ```bash theme={null} amp deploy ./integrations --project my-project ``` ### delete:integration Deletes a specific integration from your project. **Syntax:** ```bash theme={null} amp delete:integration --project [--key ] [--debug] ``` **Notes:** * This action is irreversible. Use with caution. * Removes the integration & any associated installations from your project. **Example:** ```bash theme={null} amp delete:integration f47ac10b-58cc-4372-a567-0e02b2c3d479 --project my-project ``` ### delete:installation Deletes a specific installation of an integration. **Syntax:** ```bash theme={null} amp delete:installation --project [--key ] [--debug] ``` **Notes:** * This removes a specific installation of an integration, not the integration itself. * Requires both the integration ID and installation ID. **Example:** ```bash theme={null} amp delete:installation 550e8400-e29b-41d4-a716-446655440000 91c38b0b-814c-4edd-8221-7e4f70a90123 --project my-project ``` ## Resource commands These commands help you view and manage various resources within your Ampersand projects. Use them to inspect available destinations, current installations, integrations, and projects associated with your account. ### list:destinations Lists all available destinations in your project. For more details on Destinations, refer to the [Ampersand Destinations](https://docs.withampersand.com/destinations) Documentation. **Syntax:** ```bash theme={null} amp list:destinations --project [--key ] [--debug] ``` **Notes:** * The output includes destination IDs and names. **Example:** ```bash theme={null} amp list:destinations --project my-project ``` ### list:installations Displays all current installations of a given integration in your project. **Syntax:** ```bash theme={null} amp list:installations --project [--key ] [--debug] ``` **Example:** ```bash theme={null} amp list:installations 550e8400-e29b-41d4-a716-446655440000 --project my-project ``` ### list:integrations Provides a list of all integrations in your specified project. **Syntax:** ```bash theme={null} amp list:integrations --project [--key ] [--debug] ``` **Example:** ```bash theme={null} amp list:integrations --project my-project ``` ### list:projects Displays all projects associated with your Ampersand account. **Syntax:** ```bash theme={null} amp list:projects [--key ] [--debug] ``` **Notes:** * Lists your projects to help identify available projects when using other commands. Note that this command only displays project information and does not provide interactive project selection. * Shows project IDs and names. **Example:** ```bash theme={null} amp list:projects ``` # Concepts Source: https://docs.withampersand.com/concepts ## Integration An **integration** defines how your application will interact with a third party SaaS tool. You will have a single integration for all of your customers that are connecting your application with a particular SaaS product. For example, you might have an integration called `mySalesforceIntegration`. Salesforce is the **provider** that we are integrating with. You create an integration by defining it in a [manifest file](/manifest-reference) and then deploying it using the `amp` CLI. (See [CLI Overview](/cli/overview)). An **installation** is specific to a single customer that sets up one of your integrations to connect with their SaaS instance. For example, `mySalesforceIntegration` might have 30 installations - one for each of your customers that have connected their Salesforce instance with your application. ## Installation An installation is an instance of an integration, for a particular customer. It is created: * when a customer connects their SaaS instance in an [embeddable UI component](/embeddable-ui-components) * or when you make an API call to the [CreateInstallation](/reference/installation/create-a-new-installation) endpoint. An installation captures the following information: * the credentials for connecting to the customer's SaaS instance. (Ampersand supports OAuth, Basic Auth, and API Key based authentication.) We call this the **connection**. * a customer's preference for which objects and fields they want your application to read or write, and fields they've mapped. We call this the **config**. *** For other terminology, refer to [Terminology](/terminology). # BigQuery Source: https://docs.withampersand.com/customer-guides/bigquery This guide walks you through connecting your Google BigQuery dataset to an integration using a GCP service account. ## Before installing a BigQuery integration You will need access to a Google Cloud Platform (GCP) project that contains the BigQuery dataset you want to connect. You must have sufficient permissions to create service accounts and manage IAM roles (typically **Owner** or **Editor** access). ### 1. Enable the required APIs 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). 2. Select your project from the project picker at the top of the page. 3. Navigate to **APIs & Services** > **Enabled APIs & Services**. 4. Search for **BigQuery API** and verify it is enabled. If not, click **Enable**. 5. Also search for and enable the **BigQuery Storage API**, as this is required for high-performance data reads. ### 2. Create a service account A service account is a special-purpose GCP account used by applications to access resources. 1. In the Google Cloud Console, navigate to **IAM & Admin** > **Service Accounts**. 2. Click **Create Service Account**. 3. Enter a name (e.g., "Integration Service Account") and an optional description. 4. Click **Create and Continue**. 5. Grant the following roles: * **BigQuery Data Viewer** — allows reading table data and metadata. * **BigQuery Job User** — allows running queries in the project. 6. Click **Done**. If you only want to grant access to a specific dataset rather than the entire project, you can assign the **BigQuery Data Viewer** role at the dataset level instead. Navigate to the dataset in the BigQuery console, click **Sharing** > **Permissions**, and add the service account there. ### 3. Create and download a key 1. From the **Service Accounts** page, click on the service account you just created. 2. Go to the **Keys** tab. 3. Click **Add Key** > **Create new key**. 4. Select **JSON** as the key type and click **Create**. 5. A JSON file will be downloaded. Store it securely. This file contains credentials that grant access to your BigQuery data. You will need to base64-encode the contents of this file before providing it during installation. Use the command that matches your environment: **macOS (Terminal)** ```bash theme={null} base64 -i service-account-key.json ``` **Linux, Git Bash, or WSL** ```bash theme={null} base64 -w 0 service-account-key.json ``` **Windows (PowerShell)** ```powershell theme={null} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'service-account-key.json'))) ``` Copy the encoded string (a long single line). You will paste it when installing the integration. ### 4. Gather your connection details You will need the following information when installing the integration: | Input | Where to find it | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Service Account Key** | The base64-encoded contents of the JSON key file from step 3. | | **GCP Project ID** | Your project's ID (not the project number). Find it at the top of the Google Cloud Console or under **IAM & Admin** > **Settings**. It looks like `my-project-id`, not a number. | | **Dataset Name** | The name of the BigQuery dataset you want to connect. Find it in the BigQuery console under your project in the left sidebar. | | **Timestamp Column** | The name of a `TIMESTAMP` or `DATETIME` column in your tables that tracks when rows were last modified (e.g., `updated_at`, `modified_date`). This is used for incremental syncing. | ## Installing the integration When you are installing the integration, you will be prompted for the inputs listed above. After providing them, the integration will begin syncing data from your BigQuery dataset. ## Troubleshooting ### Access blocked despite valid credentials If your GCP project is protected by [VPC Service Controls](https://cloud.google.com/vpc-service-controls/docs/overview), all external API requests are denied by default, including the BigQuery Storage Read API used by this integration. Ask your GCP administrator to create an ingress rule that allows the service account to access `bigquery.googleapis.com` from outside the perimeter. ### Missing or filtered data If columns in your tables use [column-level security](https://cloud.google.com/bigquery/docs/column-level-security-intro) (Data Catalog policy tags), the service account also needs the `Fine-Grained Reader` role on those policy tags. If your tables use [row-level security](https://cloud.google.com/bigquery/docs/row-level-security-intro), rows may be silently filtered. Please ensure the service account is included in the relevant row access policies. # Google (workspace delegation) Source: https://docs.withampersand.com/customer-guides/google-workspace-delegation This guide walks you through connecting your Google Workspace domain to an integration that uses a GCP service account with [domain-wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority). This is a headless, server-to-server authentication method — there is no interactive login, and the integration accesses Gmail, Calendar, and Contacts data on behalf of users in your domain. ## Before installing a Google (Workspace Delegation) integration You will need: * **Google Cloud Platform (GCP)** access with permission to create service accounts (typically **Owner** or **Editor** on a GCP project). * **Google Workspace super-admin** access to your domain (required to authorize domain-wide delegation). ### 1. Enable the required APIs 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). 2. Select (or create) a GCP project that will own the service account. 3. Navigate to **APIs & Services** > **Library**. 4. Enable the APIs your integration needs: * **Gmail API** for Gmail integrations. * **Google Calendar API** for Calendar integrations. * **People API** for Contacts integrations. ### 2. Create a service account 1. In the Google Cloud Console, navigate to **IAM & Admin** > **Service Accounts**. 2. Click **Create Service Account**. 3. Enter a name and an optional description. 4. Click **Create and Continue**, then **Done**. You do not need to grant any project-level IAM roles for domain-wide delegation as the authorization happens in the Workspace Admin console in step 4. ### 3. Create and download a key 1. From the **Service Accounts** page, click on the service account you just created. 2. Note the **Unique ID** (a numeric client ID, e.g. `123456789012345678901`). You will need this in the next step. 3. Go to the **Keys** tab. 4. Click **Add Key** > **Create new key**. 5. Select **JSON** as the key type and click **Create**. 6. A JSON file will be downloaded. Store it securely — this file contains credentials that can impersonate any user in your Workspace domain once delegation is authorized. You will need to base64-encode the contents of this file before providing it during installation. Use the command that matches your environment: **macOS (Terminal)** ```bash theme={null} base64 -i service-account-key.json ``` **Linux, Git Bash, or WSL** ```bash theme={null} base64 -w 0 service-account-key.json ``` **Windows (PowerShell)** ```powershell theme={null} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'service-account-key.json'))) ``` Copy the encoded string (a long single line). You will paste it when installing the integration. ### 4. Authorize domain-wide delegation This step grants the service account permission to impersonate users in your Workspace domain. It must be performed by a **super-admin** of the Google Workspace domain. 1. Go to the [Google Admin console](https://admin.google.com/). 2. Navigate to **Security** > **Access and data control** > **API controls**. 3. Click **Manage Domain Wide Delegation**. 4. Click **Add new**. 5. In **Client ID**, paste the numeric **Unique ID** of the service account from step 3. 6. In **OAuth scopes**, enter a comma-separated list of the scopes your integration needs. For example: * Gmail (read/write): `https://mail.google.com/` * Gmail (read-only): `https://www.googleapis.com/auth/gmail.readonly` * Calendar (read/write): `https://www.googleapis.com/auth/calendar` * Calendar (read-only): `https://www.googleapis.com/auth/calendar.readonly` * Contacts (read/write): `https://www.googleapis.com/auth/contacts` * Contacts (read-only): `https://www.googleapis.com/auth/contacts.readonly` 7. Click **Authorize**. Changes to domain-wide delegation can take up to 24 hours to propagate, though most take only a few minutes. If the integration returns `unauthorized_client` errors immediately after installation, wait and retry. ### 5. Gather your connection details You will need to share the following information with the integration builder: | Input | Where to find it | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | **Service Account Key** | The base64-encoded contents of the JSON key file from step 3. | | **User Emails** | A full list of email addresses of all the users whose data the integration should access (e.g. `user1@company.com, user2@company.com`). | | **Scopes** | The exact list of OAuth scopes you authorized in step 4 (or a subset). | ## Troubleshooting ### unauthorized\_client error Sample error message: `unauthorized_client: Client is unauthorized to retrieve access tokens using this method` This means that either: * The service account's numeric Client ID was not added to **Manage Domain Wide Delegation** in the Admin console (revisit step 4), or * One or more scopes provided to the integration were not a part of the scopes authorized in step 4. ### invalid\_grant error Sample error message: `invalid_grant: Invalid email or User ID` This means that a **user email** you provided is not a valid active user in the Workspace domain. Workspace delegation does not work for suspended or deleted users. Confirm that the user's address exists in **Admin console** > **Directory** > **Users**. # Housecall Pro Source: https://docs.withampersand.com/customer-guides/housecallPro ## Checklist before you start Confirm the following: * You are a Housecall Pro **Admin** user. * Your Housecall Pro account has a **MAX subscription**. ## 1. Generate your Housecall Pro API key Follow these steps to generate your Housecall Pro API key. 1. Click the **My Apps** icon (3x3 grid) in the top-right corner. 2. Click **Go to App Store**. 3. Under **My Apps**, find and open the **API Key Management** app card. 4. Click **Generate API Key**. 5. Enter a descriptive name for the key. 6. Copy the API key and store it securely. ## 2. Set up webhooks for real-time updates (Subscribe Actions) Skip this section if your developer did not give you a webhook URL, or if you do not need real-time updates. Follow these steps to set up webhooks in Housecall Pro. 1. Click the **My Apps** icon (3x3 grid) in the top-right corner. 2. Click **Go to App Store**. 3. Under **All apps**, find and open **Webhooks**. 4. Set Webhooks to **Active**, paste the link the developer gave you into the **Webhook URL** field, then click **Save**. 5. Enable the webhook events that you want to subscribe to, then click **Save**. **`pro.created`** in Housecall Pro means a new **employee**; the connected product may display it as **`employee.created`**. ## For official setup details, see: * [Housecall Pro API Overview](https://help.housecallpro.com/en/articles/8505035-api-overview#h_3b0cd3e648) * [Housecall Pro Webhooks Setup](https://help.housecallpro.com/en/articles/5683520-how-to-enable-webhooks) # HubSpot Source: https://docs.withampersand.com/customer-guides/hubspot ## Checklist before installing a HubSpot integration We recommend verifying the following requirements before installing the HubSpot integration to ensure a smooth setup. ### 1. Ensure you are a HubSpot Super Admin You must be a **Super Admin** to install an app. This is a [HubSpot requirement](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/authentication/oauth/oauth-quickstart-guide). ### 2. Ensure you have a Sales Hub seat (if access to Leads is required) If your integration requires access to Leads, you must have a Sales Hub seat in HubSpot. Without this, you may not be able to retrieve or manage lead data. To check if you have a Sales Hub seat: 1. Log in to *HubSpot*. 2. Click on your profile picture and select **Settings**. 3. In the left sidebar, navigate to **Users & Teams**. 4. Locate your user account and check for **Sales Hub seat** assignment. 5. If you do not have a Sales Hub seat, ask your HubSpot administrator to assign one to you. Learn more about different HubSpot seats [here](https://knowledge.hubspot.com/account-management/manage-seats). # Loxo Source: https://docs.withampersand.com/customer-guides/loxo ## Checklist before installing a Loxo integration Before beginning the installation, you’ll need to gather a few metadata values from your Loxo account. The steps below outline where to locate each of these values. ### 1. Full Domain Log in to your Loxo account and check the URL in your browser’s address bar. It typically follows this pattern: `https://[your-domain-here]`. loxo-full-domain ### 2. Agency Slug Go to **Settings > Workspace > General**. In the **Agency Info** section, check the **Resume Forwarding Address**. The text before the '@' symbol is your **Agency Slug**. loxo-agency-slug # Marketo Source: https://docs.withampersand.com/customer-guides/marketo ## Checklist before installing a Marketo integration We recommend completing the following steps before installing the Marketo integration to ensure a smooth setup. ### 1. Create an API-Only Role 1. Log in to **Adobe Experience**. 2. Click on **Marketo Engage** and launch your instance. 3. At the top, navigate to **Admin**. 4. In the left sidebar, go to **Admin** → **Security** → **Users & Roles**. 5. Click on **Roles**, then **New Role**, and provide the API role name (used in Step 2). 6. Select the appropriate API access permissions for this role.\\ Img 7. Click **Create**. For more information, refer to [Marketo's official documentation](https://experienceleague.adobe.com/en/docs/marketo/using/product-docs/administration/users-and-roles/create-an-api-only-user-role). ### 2. Create an API-Only User 1. Log in to **Adobe Experience**. 2. Click on **Marketo Engage** and launch your instance. 3. At the top, navigate to **Admin**. 4. In the left sidebar, go to **Admin** → **Security** → **Users & Roles**. 5. Click on **Users**, then **Create API Only User**, and fill in the required details. 6. Under **Roles**, select the role created in Step 1.\\ Img 7. Click **Create**. For more information, refer to [Marketo's official documentation](https://experienceleague.adobe.com/en/docs/marketo/using/product-docs/administration/users-and-roles/create-an-api-only-user). ### 3. Create a Custom Service 1. Log in to **Adobe Experience**. 2. Click on **Marketo Engage** and launch your instance. 3. At the top, navigate to **Admin**. 4. In the left sidebar, go to **Admin** → **Integration** → **LaunchPoint**. 5. Click on **New** → **New Service**, and provide the following details: * **Display Name**: (Your preference) * **Service**: Custom * **Description**: (Your preference) * **API Only User**: Select the user created in Step 2\\ Img 6. Click **Create**. 7. After creation, you can view the **Client ID** and **Client Secret** by clicking **View Details**.\\ Img 8. You will also need the `workspaceId` (Munchkin Account ID). This can be found under **Admin** → **Integration** → **Munchkin**.\\ Img For more information, refer to [Marketo's official documentation](https://experienceleague.adobe.com/en/docs/marketo/using/product-docs/administration/additional-integrations/create-a-custom-service-for-use-with-rest-api#_blank). # NetSuite Source: https://docs.withampersand.com/customer-guides/netsuite This guide walks you through connecting your NetSuite account to an integration using OAuth 2.0 Client Credentials (Machine to Machine). ## Before installing a NetSuite integration You will need **Administrator access** to your NetSuite account. ### 1. Install the bundle 1. Navigate to **Customization** > **SuiteBundler** > **Search & Install Bundles**. 2. Search for the bundle name or ID. 3. Select the bundle from the results and click **Install**. 4. Wait for the installation to complete. This may take a few minutes. The bundle will install SuiteScripts and custom objects (fields, records, roles, etc) into your NetSuite account. ### 2. Verify the deployment 1. Navigate to **Customization** > **Scripting** > **Script Deployments**. 2. Find the RESTlet deployment installed by the bundle and click on 'View'. 3. Verify the deployment **Status** is **Released**. 4. Click on the name of the script, then note the **URL** — you will need it for the installation step. ### 3. Enable required features 1. Log in to *NetSuite* as an Administrator. 2. Navigate to **Setup** > **Company** > **Enable Features** > **SuiteCloud**. 3. Under **Manage Authentication**, check **OAuth 2.0**. 4. Under **SuiteScript**, check **Server SuiteScript**. 5. Under **Manage Authentication** (or **SuiteTalk**), check **REST Web Services**. 6. Make sure that the following features are also enabled in your environment: * Accounting * Custom Records * File Cabinet 7. Click **Save**. ### 4. Create an integration record The integration record identifies the external application that will connect to your NetSuite account. 1. Navigate to **Setup** > **Integration** > **Manage Integrations** > **New**. 2. Enter a **Name** for the integration (e.g. "Ampersand Integration"). 3. Under the **Token-based Authentication** section: * Uncheck **TBA: Authorization Flow** * Uncheck **Token-Based Authentication** 4. Under the **OAuth 2.0** section: * Uncheck **Authorization Code Grant** * Check **Client Credentials (Machine to Machine) Grant** 5. Under **Scope**, check the following: * **RESTlets** * **REST Web Services** 6. Click **Save**. 7. After saving, NetSuite displays the **Consumer Key / Client ID** under the **Client Credentials** section. Copy this value and store it securely — it is shown only once and cannot be retrieved later. ### 5. Generate a certificate key pair In this step you'll use your computer's terminal to create two files: a private key and a self-signed certificate. This is a one-time operation — if anything goes wrong, you can delete the files and start over. Before you begin, you need **OpenSSL** available in your terminal. macOS and most Linux distributions include it by default. On **Windows**, use **Git Bash** (bundled with [Git for Windows](https://gitforwindows.org/), which includes OpenSSL), **WSL**, or install OpenSSL and ensure it is on your `PATH`. #### a. Create a folder and open a terminal in it Create a new folder anywhere you'll remember — your Desktop works fine. For example, create one called `netsuite-keys`. Then open a terminal *inside that folder* so the files you generate end up there: * **macOS**: In **Finder**, right-click the folder and choose **New Terminal at Folder**. (If you don't see this option, enable it under **System Settings** > **Keyboard** > **Keyboard Shortcuts** > **Services** > **Files and Folders** > **New Terminal at Folder**.) * **Windows**: In **File Explorer**, right-click inside the folder and choose **Open in Terminal** or **Open Git Bash here**. * **Any OS (fallback)**: Open your terminal app, type `cd ` (with a trailing space), drag the folder from your file browser onto the terminal window, and press **Enter**. #### b. Run the OpenSSL commands Copy each command below, paste it into the terminal right after the prompt (`%` on macOS, `$` on Linux/Git Bash, `PS>` on PowerShell), and press **Enter**. Run them one at a time. ```bash theme={null} openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem ``` ```bash theme={null} openssl req -new -x509 -key private-key.pem -out cert.pem -days 730 ``` The second command will prompt you for certificate details (country, organization name, email, etc.). **Press Enter at every prompt to accept the defaults** — these values are not used by the integration, so it doesn't matter what they are. #### c. Confirm the two files were created If the commands succeeded, your folder now contains two files: * `private-key.pem` — your private key. You'll base64-encode it in the next sub-step. * `cert.pem` — your public certificate. You'll upload it to NetSuite in step 7. Open the folder in **Finder** (macOS) or **File Explorer** (Windows) to confirm both files are there. If you can't see them, search your computer for `cert.pem` — the files were created in whichever folder your terminal was open in. If anything looks wrong — missing files, error messages, accidentally hit Enter too many times — just delete the files and run the commands again. The keys aren't registered anywhere until you upload `cert.pem` to NetSuite in step 7, so there's no risk in starting over. #### d. Base64-encode the private key Run the command below to print a base64-encoded version of your private key directly in the terminal. Use the version that matches your environment: **macOS (Terminal)** ```bash theme={null} base64 -i private-key.pem ``` **Linux, Git Bash, or WSL** ```bash theme={null} base64 -w 0 private-key.pem ``` **Windows (PowerShell)** ```powershell theme={null} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'private-key.pem'))) ``` After running the command, you should see a long single-line string printed in the terminal, starting with `LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0t...`. Select and copy the entire string — you'll paste it during the installation process. ### 6. Assign the integration role to a user or create a new user The bundle will include a custom role with the permissions needed for the integration. You need to assign this role to the employee/user account that the integration will run as. 1. Navigate to **Setup** > **Users/Roles** > **User Management** > **Employees** (or **Lists** > **Employees**). 2. Find and edit the employee record you want the integration to run as. This can be an existing user or a dedicated integration user. 3. Go to the **Access** tab. 4. In the **Roles** subtab, click **Add**. 5. Select the custom role from the dropdown. 6. Click **Save**. If you prefer to use a dedicated integration user rather than an existing employee, create a new user/employee record first, then assign the role as described above. This is recommended for security purposes. ### 7. Create a machine-to-machine certificate mapping 1. Navigate to **Setup** > **Integration** > **OAuth 2.0 Client Credentials (M2M) Setup**. 2. Click **Create New**. 3. Configure the following fields: * **Entity**: Select the employee/user account the integration will run as (from the previous step) * **Role**: Select the custom role that the bundle installed. Please note that the role must include the **Log in Using OAuth 2.0 Access Tokens** permission. * **Application**: Select the integration record you created in step 4. * **Certificate**: Upload the `cert.pem` file you generated in step 5. 4. Click **Save**. 5. Copy the **Certificate ID** from the list — you will need this value when installing the integration. Check that the `Valid Until` date for the certificate is two years in the future. NetSuite allows a maximum validity of 730 days. ## Installing the integration When you are installing the integration, you will be prompted for a number of inputs: Here is where to find each value: | Input | Where to find it | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **Client ID** | The Consumer Key from step 2. | | **Certificate ID** | From step 4. | | **Private Key** | Base64-encoded contents of `private-key.pem` (from step 3) | | **Account ID** | Your NetSuite URL (e.g. `https://1234567.app.netsuite.com` → Account ID is `1234567`), or **Setup** > **Company** > **Company Information**. | | **RESTlet Deployment URL** | This is printed out in the Deployments tab of the script (see previous step), and looks like `/app/site/hosting/restlet.nl?script=3045&deploy=1`. | **Sandbox accounts**: If you are connecting a sandbox account, your Account ID may contain an underscore and suffix (e.g. `1234567_SB1`). Provide it exactly as shown in your NetSuite URL or Company Information page. # Overview Source: https://docs.withampersand.com/customer-guides/overview Welcome to the Ampersand **Customer Guides** section! Customer guides are specifically designed for end users who are setting up or troubleshooting integrations built with Ampersand. While our Provider Guides are intended for developers building integrations, these guides focus on clear, step-by-step instructions for anyone—whether or not they have a technical background. With the customer guides, we want to simplify the setup process for you. Each guide covers common setup steps, troubleshooting tips, and best practices to ensure a smooth and hassle-free experience. ## What you'll find here * Step-by-step instructions for common integration platforms * Troubleshooting tips for common issues * Prerequisites checklists before installation * Visual aids including screenshots and GIFs to guide you through complex processes # Salesforce Source: https://docs.withampersand.com/customer-guides/salesforce This guide helps you set up your Salesforce org to work with an Ampersand-powered integration. The steps differ depending on whether the integration uses an **External Client App** or a **Connected App**. If you're not sure which one you're using, ask the developer who built the integration. ## Before installing a Salesforce integration ### Verify your Salesforce edition supports API access Salesforce editions offer different levels of API access, which can affect how Salesforce integrations work in Ampersand. Before setting up your Salesforce integration, confirm whether your Salesforce edition includes API access. Some editions include full API access by default, while others may offer limited access or require additional purchases. To check if your Salesforce edition supports the necessary API access for this integration, please refer to [Salesforce's official documentation](https://help.salesforce.com/s/articleView?language=en_US\&id=000385436\&type=1) on API access by edition. If your Salesforce edition does not include the required API access, contact your Salesforce account representative to upgrade your edition. ## Setting up the integration ### Install the package If the integration uses an External Client App, the developer will provide you with a **package install URL**. This URL installs the app into your Salesforce org. 1. Open the package install URL provided by the developer. 2. Log in to your Salesforce org if prompted. 3. Select **Install for All Users**. 4. Check the acknowledgment checkbox. 5. Click **Install**. You will see a progress screen while the installation completes. Once complete, you should see a confirmation screen: To verify the package was installed, go to **Setup**, search for **Installed Packages**, and confirm the package appears in the list. You must complete this step **before** connecting your Salesforce account through the integration's UI, otherwise the OAuth connection will fail. ### Configure token policy settings Salesforce access tokens expire after a certain period. To ensure your integration continues working without interruption, you need to set your refresh token policy to **Refresh token is valid until revoked**. 1. Log in to *Salesforce*. 2. Go to **Setup**. 3. In the *Quick Find* box, search for **Connected Apps**. 4. Click on **Manage Connected Apps**. 5. Find and click on the name of the application you are integrating with. 6. Scroll down to the **OAuth Policies** section. 7. Look for **Refresh Token Policy**. 8. Under *IP Relaxation*, select **Relax IP restrictions**. 9. Make sure the refresh token policy is set to **Refresh token is valid until revoked**. Refresh Token Settings 10. Click **Save**. ### Manage app policies Salesforce allows administrators to restrict which applications can access Salesforce data through APIs. You need to ensure that API access isn't limited to only specific connected apps. 1. Log in to *Salesforce*. 2. Go to **Setup**. 3. In the *Quick Find* box, search for **Connected Apps OAuth Usage**. 4. Under the list of connected apps, find the one for this integration and click **Manage App Policies**. 5. In the *OAuth Policies* section, ensure the **Permitted Users** status is one of: * *Admin approved users are pre-authorized* — only selected users can access. * *All users may self-authorize* — all users can access the app. Permitted Users If you need to modify these settings: 1. Click on **Install** next to your connected app. 2. In the **OAuth Usage and Policies** section, set the appropriate permissions level. 3. Click **Save**. ## Ensure sufficient permissions The credentials provided to the integration can be: * A [human user](#human-users), such as: * a System Administrator. Please note that you still need to ensure that the System Admin has the correct object and field level permissions, they may not be granted by default. * a sales team member with the **Salesforce** User License and the necessary permissions as specified below. This user can either have a standard profile (such as "Standard User") or a custom profile. Please note that the **Salesforce Platform** User License is insufficient. * A [Salesforce integration user](#salesforce-integration-users). This type of user is specifically for integrations, and does not have access to the Salesforce UI. ### Human users #### 1. Configure system permissions In **Setup**, search for **Profiles** in the Quick Find box and open it. Then: 1. Select the profile you'd like to view and edit. 2. Click **Edit** at the top of the page. Edit profile 3. Ensure the checkboxes for the necessary [system permissions](#system-permissions-needed) are checked. 4. Click **Save** at the top or bottom of the page. #### 2a. Field permissions for standard profile If the user has a standard profile (such as "Standard User"), then follow these instructions: 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Object Manager** and open it. Setup Object Manager 3. Choose the object you need (for example, **Account**), then select **Fields & Relationships** from the left navbar. Setup Object Manager Account 4. Find the field you want to adjust and click it. 5. Click **Set Field-Level Security**. Set Field Level Security 6. Ensure the checkbox for **Visible** is selected for the profile you're interested in. If the profile is not visible in this list, it means that it does not have access to the object. This is not possible to modify. Check Visible for profile 7. Repeat steps 4–6 for all fields that the integration needs to read, especially custom fields. #### 2b. Object and field permissions for custom profile If the user has a custom profile, then follow these instructions: 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Object Manager** and open it. Setup Object Manager 3. Choose the object you need (for example, **Account**) and go to **Object Access** in the left navbar. Select the **Profiles** tab at the top. Click **Edit** and grant the necessary permissions for your custom profile. * If the integration needs to read data, ensure that `Read`, `View All Records`, and `View All Fields` are checked. * If the integration needs to write data, ensure that all boxes are checked. Edit Object Access ### Salesforce Integration users The Salesforce Integration user license type is a special type of license that can be used for integrations and does not have UI access. #### 1. Create a new user 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Users** and open it. 3. Create a new user: * For **User License**, select `Salesforce Integration`. * For **Profile**, select `Minimum Access - API Only Integrations`. #### 2. Create a Permission Set 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Permission Sets** and open it. 3. Click on "New" to create a new permission set. 4. Follow the prompts to create the permission set: * For the name, you can call it something general like `Integration User Permission Set` or something that describes the level of access it has - such as `Account and Contact Access`. * In the **License** dropdown, you must select `Salesforce API Integration`. #### 3. Configure object permissions 1. Click on **Object Settings** 2. For each of the objects that the integration needs to access you need to set up its permissions. Start by clicking on the first object, for example "Accounts". 3. Edit the permission so that all relevant boxes under **Object Permissions** and **Field Permissions** are checked. Then click on **Save**. 4. Repeat for all the other objects that the integration needs to access. #### 4. Configure system permissions 1. Select **System Permissions**. 2. Ensure that the checkboxes for the necessary [system permissions](#system-permissions-needed) are selected, and then click **Save**. #### 5. Assign permission set to integration user 1. Click on **Manage Assignments**. 2. Click on **Add Assignment**. 3. Select the integration user you created in step 1. 4. Ensure "Expires On" is set to "Never Expires", and then click on "Assign". ## After installing the integration After the integration is installed, you need to configure OAuth policies to ensure the integration can maintain a stable connection. 1. Log in to *Salesforce*. 2. Go to **Setup**. 3. In the *Quick Find* box, search for **External Client App Manager**. 4. Click on the name of the installed External Client App. 5. Go to the **Policies** tab and click **Edit**. 6. Under **OAuth Policies**, ensure **Permitted Users** is set to **All users may self-authorize**. 7. Under **App Authorization**, make the following changes: * Set **Refresh Token Policy** to **Refresh token is valid until revoked**. * Set **IP Relaxation** to **Relax IP restrictions**. 8. Click **Save**. Configure ECA OAuth Policies Salesforce allows administrators to restrict which applications can access Salesforce data through APIs. You need to ensure that API access isn't limited to only specific connected apps. To verify your API access control settings: 1. Log in to *Salesforce*. 2. Go to **Setup**. 3. In the *Quick Find* box, search for **Connected Apps OAuth Usage**. 4. Under the list of connected apps, find the one for this integration and click **Manage App Policies**. 5. In the *OAuth Policies* section, ensure the **Permitted Users** status is one of: * *Admin approved users are pre-authorized* - only selected users can access. * *All users may self-authorize* - all users can access the app. Permitted Users If you need to modify these settings: 1. Click on **Install** next to your connected app. 2. In the **OAuth Usage and Policies** section, set the appropriate permissions level. 3. Click **Save**. ## Salesforce terminology ### License A **license** is purchased from Salesforce and determines the maximum set of permissions that can be granted to a user with that license. ### Profile A **profile** is a collection of settings that determines what objects, fields, and features a user can access in Salesforce. Each user has exactly one profile. Standard profiles (e.g. Standard User, System Administrator) are provided by Salesforce; you can also create custom profiles by cloning an existing one. A profile is always associated with one user license, but a user license can have multiple profiles associated with it. When you create a new user, you can pick both the license and profile of that person. ### Permission set A **permission set** is a collection of permissions that you assign to users to grant access to specific objects, fields, and capabilities. Permission sets extend the default permissions of a profile. Users can have only one profile but multiple permission sets. A permission set is tied to a license, and only users with that license type can be granted the permission set. ## System permissions needed The following system permissions are needed: * `API Enabled` * [Subscribe Action Permissions](#subscribe-action-permissions) if the integration includes real-time Subscribe Actions. The following system permissions are needed: * `API Enabled` * [Subscribe Action Permissions](#subscribe-action-permissions) if the integration includes real-time Subscribe Actions. * One of the following: * `Use Any API Client` if you see it. (You will only see this option if your organization has enabled [API Access Control](https://help.salesforce.com/s/articleView?id=xcloud.security_api_access_control_about.htm\&type=5)). * `Approve Uninstalled Connected Apps` if you do not see an option for `Use Any API Client`. To ensure that you have the necessary system permission, please follow these instructions: * [For a Profile](#1-configure-system-permissions) * [For a Permission Set that is assigned to an Integration User](#4-configure-system-permissions) ## Subscribe Action permissions If the integration contains Subscribe Actions, a number of special permissions are required. Here's an explanation of why each permission is necessary: These permissions need to be explicitly enabled: * `Modify Metadata Through Metadata API Functions`: Required to configure event channels and event channel memberships through Metadata API. * `Customize Application`: Required to configure artifacts like Named Credentials, which allows Ampersand to securely connect to the event channels. Salesforce auto-enables these when you enable the above permissions, because they dependent permissions: * `View Setup and Configuration`: Enables the integration to access Salesforce setup configuration in order to create webhook subscription settings. * `View Roles and Role Hierarchy`: Ensures the integration has the correct visibility context so Salesforce can successfully send events; without this, events may be generated but not delivered. * `Manage Custom Permissions`: Allows the integration to create and manage dedicated event channels and channel memberships used specifically for the installation's subscription events. To ensure that you have the necessary permission to use Subscribe Actions, please follow these instructions: * [For a Profile](#1-configure-system-permissions) * [For a Permission Set that is assigned to an Integration User](#4-configure-system-permissions) # Salesforce (JWT authentication) Source: https://docs.withampersand.com/customer-guides/salesforce-jwt This guide walks you through connecting your Salesforce org to an integration that uses the [OAuth 2.0 JWT Bearer Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm\&language=en_US\&type=5). This is a headless, server-to-server authentication method — there is no interactive login, and the integration runs on behalf of a designated user using credentials you configure up front. If your integration uses the standard Salesforce OAuth flow instead, please refer to the [Salesforce customer guide](/customer-guides/salesforce). If you are unsure which guide is applicable to you, please ask the integration developer. ## Before installing a Salesforce (JWT) integration You will need **Administrator access** to your Salesforce org. ### 1. Generate a certificate and RSA private key The JWT Bearer flow requires an RSA key pair. You'll upload the public certificate to your Salesforce External Client App and share the private key (base64-encoded) with the integration. Open a terminal in the folder where you want to create the files. You need **OpenSSL** available in that environment (macOS and most Linux distributions include it by default). On **Windows**, use **Git Bash** (bundled with [Git for Windows](https://gitforwindows.org/), which includes OpenSSL), **WSL**, or install OpenSSL and ensure it is on your `PATH`. Run the following command to generate a 2048-bit RSA private key and a matching self-signed X.509 certificate in a single step: ```bash theme={null} openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 \ -keyout private-key.pem -out cert.pem \ -subj "/CN=salesforce-jwt-integration" ``` This produces two files: * `private-key.pem` — Your RSA private key. You will base64-encode this and share it when installing the integration. * `cert.pem` — Your public certificate. You will upload this to Salesforce in a later step. Salesforce requires an RSA key. ECDSA (`ecparam`) keys are not accepted for the JWT Bearer flow. Use a key length of at least 2048 bits. You will then need to base64-encode your private key before sharing it during the installation process. Use the command that matches your environment: **macOS (Terminal)** ```bash theme={null} base64 -i private-key.pem ``` **Linux, Git Bash, or WSL** ```bash theme={null} base64 -w 0 private-key.pem ``` **Windows (PowerShell)** From the directory that contains `private-key.pem`: ```powershell theme={null} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'private-key.pem'))) ``` Copy the encoded string (a long single line, e.g. starting with `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tL...`). It will be required during the installation process. ### 2. Create the External Client App The External Client App identifies the integration to Salesforce and holds the certificate used to verify JWT signatures. 1. Log in to *Salesforce* as an Administrator. 2. Navigate to the gear icon in the top right corner and select **Setup**. Alt text 3. Search for **External Client App Manager**. 4. Click **New External Client App**. New ECA 5. Fill in the basic information: * **External Client App Name**: e.g. `My Integration` * **API Name**: auto-filled from the name * **Contact Email**: your administrator email * **Distribution State**: **Local** 6. Under **API (Enable OAuth Settings)**: * Check **Enable OAuth**. * For **Callback URL**, enter `https://api.withampersand.com/callbacks/v1/oauth`. The JWT Bearer flow does not use the callback, but Salesforce requires a value. * Under **Selected OAuth Scopes**, add: * **Manage user data via APIs (`api`)** * **Perform requests at any time (`refresh_token`, `offline_access`)** — this is required for Salesforce to accept JWT Bearer exchanges even though no refresh token is issued. 7. Under **Flow Enablement**, check **Enable JWT Bearer Flow** and upload the `cert.pem` file you generated in the previous step. 8. Under **Security**, ensure **Require Proof Key for Code Exchange (PKCE) extension for Supported Authorization Flows** is unchecked. (It's unchecked by default, but worth verifying.) Leave **Issue JSON Web Token (JWT)-based access tokens for named users** unchecked. Despite the similar name, this is a different feature that changes the format of access tokens Salesforce returns — and those JWT-format tokens only work with the REST API, not with the Bulk API or Metadata API that this integration also uses. Enabling it will break bulk writes and subscribe-action registration. 9\. Click **Create**. After saving, it may take 2 to 10 minutes for Salesforce to propagate the new External Client App. If you hit errors when testing the integration immediately after creation, wait a few minutes and try again. ### 3. Copy the Consumer Key 1. In **Setup**, open **External Client App Manager** again. 2. Select your External Client App from the list. 3. Go to the **Settings** tab and expand **OAuth Settings**. 4. Under **App Settings**, click **Consumer Key and Secret**. This opens a new page — you may be prompted to verify your identity before the values are shown. 5. Copy the **Consumer Key**. Store it securely — you will need it during the installation step. (The Consumer Secret is not needed for JWT Bearer flow.) ### 4. Configure OAuth policies 1. In **Setup**, open **External Client App Manager** and select your External Client App. 2. Go to the **Policies** tab, click **Edit**. 3. Expand **OAuth Policies**. 4. Scroll down to **App Authorization** section: * Set **Refresh Token Policy** to **Refresh token is valid until revoked**. * Set **IP Relaxation** to **Relax IP restrictions**. 5. Click **Save**. Setting **Refresh Token Policy** to **Refresh token is valid until revoked** is also required, even though the JWT Bearer flow never actually issues a refresh token. Salesforce internally checks for a non-expired approval record for the user before issuing an access token, and any other setting causes that approval to age out — which silently breaks the integration later. The other options (`Immediately expire`, `Expire after specific time`, etc.) will cause `invalid_grant` errors. ### 5. Create an integration user The JWT Bearer flow runs as a specific Salesforce user. Every API call made by the integration is attributed to this user, and its Profile and permission sets determine what data the integration can read and write. #### 1. Create a new user 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Users** and open it. 3. Create a new user: * For **User License**, select `Salesforce Integration`. * For **Profile**, select `Minimum Access - API Only Integrations`. #### 2. Create a Permission Set 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Permission Sets** and open it. 3. Click on "New" to create a new permission set. 4. Follow the prompts to create the permission set: * For the name, you can call it something general like `Integration User Permission Set` or something that describes the level of access it has - such as `Account and Contact Access`. * In the **License** dropdown, you must select `Salesforce API Integration`. #### 3. Configure object permissions 1. Click on **Object Settings** 2. For each of the objects that the integration needs to access you need to set up its permissions. Start by clicking on the first object, for example "Accounts". 3. Edit the permission so that all relevant boxes under **Object Permissions** and **Field Permissions** are checked. Then click on **Save**. 4. Repeat for all the other objects that the integration needs to access. #### 4. Configure system permissions 1. Select **System Permissions**. 2. Ensure that the checkboxes for the necessary [system permissions](/customer-guides/salesforce#system-permissions-needed) are selected, and then click **Save**. #### 5. Assign permission set to integration user 1. Click on **Manage Assignments**. 2. Click on **Add Assignment**. 3. Select the integration user you created in step 1. 4. Ensure "Expires On" is set to "Never Expires", and then click on "Assign". The username you chose when creating the user is exactly the value you'll paste into the **Salesforce Username** field when installing the integration. Copy it somewhere you can find it later. ### 6. Associate the Permission Set with the app You need to add the permission set you created in step 5 to the app's pre-authorized Permission Sets list. This is what satisfies the **Admin approved users are pre-authorized** OAuth policy from step 4 — it tells Salesforce "any user who has this permission set is allowed to use this app." 1. In **Setup**, open **External Client App Manager** and select your External Client App. 2. Go to the **Policies** tab, click **Edit**. 3. Expand **OAuth Policies**, under **Plugin Policies**, set **Permitted Users** to **Admin approved users are pre-authorized**. Click on **OK** in the dialog that pops up. 4. Expand **App Policies**, under **Select Permission Sets**, move the permission set that you created in step 5 from **Available Permission Sets** to **Selected Permission Sets** using the arrow button (`>`). 5. Click **Save**. This is the step that actually pre-authorizes the integration user for JWT Bearer. Without it — even if everything else is configured correctly — Salesforce rejects the JWT assertion with `invalid_app_access: user is not admin approved to access this app`. ### 7. Confirm your Salesforce subdomain Your Salesforce My Domain subdomain is the unique part of your Salesforce URL. You will need it to install the integration. * **Production**: if your login URL is `https://acme.my.salesforce.com`, your subdomain is `acme`. * **Sandbox (Enhanced Domains)**: if your login URL is `https://acme--dev.sandbox.my.salesforce.com`, your subdomain is `acme--dev.sandbox`. * **Sandbox (legacy)**: if your login URL is `https://acme--dev.my.salesforce.com`, your subdomain is `acme--dev`. * **Developer accounts**: if your login URL is `https://acme-dev-ed.develop.my.salesforce.com`, your subdomain is `acme-dev-ed.develop`. You can also find your subdomain in **Setup** > **My Domain**. ## Installing the integration When you are installing the integration, you will be prompted for a number of inputs. Here is where to find each value: | Input | Where to find it | | ----------------------- | ----------------------------------------------------------- | | **Consumer Key** | From step 3. | | **Salesforce Username** | The username of the integration user from step 5. | | **Private Key** | Base64-encoded contents of `private-key.pem` (from step 1). | | **Subdomain** | Your My Domain subdomain (from step 7). | **Sandbox refresh**: After refreshing a sandbox, the integration user's username may be re-suffixed with the sandbox name (e.g. `integration@acme.com` becomes `integration@acme.com.dev`). If this happens, update the **Salesforce Username** field on your Ampersand connection to match the new username. ## Troubleshooting If the integration experiences authentication errors after setup, here are some common causes. * **`invalid_app_access: user is not admin approved to access this app`**: The integration user's permission set is not in the app's **Selected Permission Sets** list. Revisit step 6 and confirm the permission set from step 5 is in **Selected Permission Sets** (not just **Available Permission Sets**), and that the permission set is actually assigned to the integration user. To reproduce the error independently, use the Salesforce CLI: `sf org login jwt --client-id --jwt-key-file private-key.pem --username --instance-url https://.my.salesforce.com`. * **`user hasn't approved this consumer`**: Either the integration user's permission set is not associated with your app (revisit step 6), or the **Refresh Token Policy** on the app is not set to **Refresh token is valid until revoked** (revisit step 4). Both are required. * **Worked for weeks or months, then started failing with `invalid_grant`**: Almost always the Refresh Token Policy silently aging out the approval. Set **Refresh Token Policy** to **Refresh token is valid until revoked** and re-associate the permission set if needed. * **`invalid_grant: audience is invalid`**: The production vs sandbox environment was auto-detected incorrectly from your subdomain. Double-check that your subdomain string matches the format shown in step 7 — sandboxes should include a `--` or `.sandbox` segment. * **`invalid_grant: invalid assertion`**: The private key or certificate is mismatched. Regenerate the pair in step 1 and re-upload the certificate in step 2. * **`not authorized`**: Check **IP Relaxation** is set to **Relax IP restrictions** (step 4), and that the integration user's permission set has **API Enabled** under **System Permissions**. * **Works immediately after setup, then fails**: Wait 2 to 10 minutes after creating or modifying the app for Salesforce to propagate the changes. # Snowflake Source: https://docs.withampersand.com/customer-guides/snowflake This provider is currently in private preview. Please reach out to us at [support@withampersand.com](mailto:support@withampersand.com) to get access. # Update a connection Source: https://docs.withampersand.com/customer-guides/update-connection If the credentials you provided for an integration need to be updated, you can use the **Manage** tab in the UI component to update your connection. ## When to update a connection Re-authentication for the connection may be required in the following scenarios: * In the **Manage** tab, the **Status** of the connection is showing as "bad\_credentials". * Your integration isn't working. * You need to switch to a different account. ## How to update a connection 1. Navigate to the **Manage** tab. Permitted Users 2. Under the **Update connection** section enter credentials as needed and click the **Next** button. ## If Manage tab isn't visible If you do not see the **Manage** tab, it means that the app builder needs to update `@amp-labs/react` to v2.7+ in their code. # Kinesis destinations Source: https://docs.withampersand.com/destinations/kinesis For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand can send the payload to your AWS Kinesis stream. ## Prerequisites Before setting up a Kinesis destination, ensure that you have: * An AWS account with access to Kinesis * A Kinesis stream (or permissions to create one) * AWS credentials with the following permissions: * `kinesis:PutRecord` * `kinesis:PutRecords` * `kinesis:DescribeStream` ## Create a Kinesis destination ### Step 1: Set up your Kinesis stream If you don't already have a Kinesis stream, create one in the AWS Console or using the AWS CLI: ```bash theme={null} aws kinesis create-stream \ --stream-name ampersand-integration-stream \ --shard-count 1 \ --region us-west-2 ``` Wait for the stream to become active, you can check by running this command: ```bash theme={null} aws kinesis describe-stream \ --stream-name ampersand-integration-stream \ --region us-west-2 ``` You should see `"StreamStatus": "ACTIVE"` in the response. ### Step 2: Create AWS credentials Create an IAM user or role with permissions to write to your Kinesis stream. You need: * **AWS Access Key ID** * **AWS Secret Access Key** * **AWS Session Token** (optional, for temporary credentials) **Example IAM policy:** ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "kinesis:PutRecord", "kinesis:PutRecords", "kinesis:DescribeStream" ], "Resource": "arn:aws:kinesis:us-west-2:123456789012:stream/ampersand-integration-stream" } ] } ``` ### Step 3: Add the destination to Ampersand Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new Kinesis destination. You'll need to provide: | Field | Type | Required | Description | | -------------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------- | | **Destination name** | string | Yes | Alias to reference in your `amp.yaml` file | | **Stream name** | string | Yes | Name of your Kinesis stream | | **Region** | string | Yes | AWS region where your stream is located (e.g., `us-west-2`) | | **AWS Access Key ID** | string | Yes | AWS access key with Kinesis permissions | | **AWS Secret Access Key** | string | Yes | AWS secret access key | | **AWS Session Token** | string | No | Session token for temporary credentials | | **Partition key template** | string | No | [JMESPath](https://jmespath.org) template for partition key | | **Endpoint URL** | string | No | Custom Kinesis endpoint if applicable (e.g. when using [LocalStack](https://www.localstack.cloud)) | Ampersand encrypts and stores your AWS credentials securely. ## Refer to the destination in your integration After creating your Kinesis destination, reference it in your `amp.yaml` file: ```yaml theme={null} specVersion: 1.0.0 integrations: - name: salesforceToKinesis displayName: Salesforce to Kinesis provider: salesforce read: objects: - objectName: account destination: ampersandKinesisStream - objectName: contact destination: ampersandKinesisStream ``` ## Message format Ampersand sends messages to your Kinesis stream in JSON format. All data is wrapped in a top-level `data` object. ### Read action messages ```json theme={null} { "data": { "action": "read", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "hubspot", "groupRef": "group-id-1", "groupName": "group-name-1", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "085353b1-07f1-4209-b471-7e028c0a68c8", "installationUpdateTime": "2025-10-14T02:58:55.595861Z", "objectName": "contacts", "operationId": "0199e0a8-149d-7ee9-9f50-0514403517f9", "operationTime": "2025-10-14T02:58:59.323405308Z", "result": [ { "fields": { "id": "438", "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane" }, "raw": { "archived": false, "createdAt": "2023-10-26T17:55:48.301Z", "id": "438", "properties": { "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane", "createdate": "2023-10-26T17:55:48.301Z" } } } ] } } ``` **Key fields:** * `action`: The action type (`read` or `subscribe`) * `projectId`: Your Ampersand project identifier * `provider`: The SaaS provider name (e.g., `hubspot`, `salesforce`) * `groupRef` / `groupName`: Customer group identifier and name * `consumerRef` / `consumerName`: End user identifier and name * `installationId`: The installation instance ID * `installationUpdateTime`: When the installation was last updated * `objectName`: The object being synced * `operationId`: Unique identifier for this sync operation * `operationTime`: When the operation completed * `result`: Array of records synced * `fields`: Normalized field data * `raw`: Original API response from the provider ### Subscribe action messages Subscribe action messages follow the same structure as read actions, but include additional event-related fields within each result entry: ```json theme={null} { "data": { "action": "subscribe", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "salesforce", "groupRef": "webhook-demo-group-id", "groupName": "webhook-demo-group-name", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "0c9230e1c-8fbe-4b28-bf10-2beee8fbf4ce", "installationUpdateTime": "2025-04-10T23:00:52.618406Z", "objectName": "company", "operationTime": "2025-04-10T23:22:25.000Z", "result": [ { "fields": { "id": "001Dp00000ZDgmxIAD", "annualrevenue": null, "website": null }, "subscribeEventType": "update", "providerEventType": "UPDATE", "raw": { "Id": "001Dp00000ZDgmxIAD", "AnnualRevenue": null, "Description": "Notes about the account", "Name": "Acme Corp", "Website": null, "attributes": { "type": "Account", "url": "/services/data/v59.0/sobjects/Account/001Dp00000ZDgmxIAD" } }, "rawEvent": { "ChangeEventHeader": { "changeOrigin": "com/salesforce/api/soap/63.0;client=SfdcInternalAPI/", "changeType": "UPDATE", "changedFields": ["Description", "LastModifiedDate"], "commitNumber": 12041091891110, "commitTimestamp": 1744327345000, "commitUser": "005Dp000003Cd1SIAS", "entityName": "Account", "recordId": "001Dp00000ZDgmxIAD", "sequenceNumber": 1, "transactionKey": "00051705-2e99-d1e5-7e7a-48e3af682d07" }, "Description": "Notes about the account", "LastModifiedDate": "2025-04-10T23:22:25.000Z" } } ] } } ``` **Additional fields in subscribe actions:** * `subscribeEventType`: Normalized event type (`create`, `update`, `delete`, `associationUpdate`) * `providerEventType`: Raw event type from the provider API * `rawEvent`: Original webhook event from the provider (when available) ## Partition key configuration Kinesis uses partition keys to distribute messages across shards. (Learn more in [the AWS docs](https://docs.aws.amazon.com/streams/latest/dev/key-concepts.html)). You can customize the partition key using a JMESPath template. JMESPath is a query language for JSON. See [jmespath.org](https://jmespath.org) for the full specification. You can specify a partition key template when creating a Kinesis destination in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). If you don't specify one, by default we use the message ID as the partition key, so the messages we send will be evenly distributed across your shards. ### Custom partition key examples You can customize the partition key by using any of the attributes inside of the message. Since all message data is wrapped in a `data` object, you need to prefix your JMESPath expressions with `data.`. Here are some examples: **Use installation ID:** ``` data.installationId ``` **Use group reference:** ``` data.groupRef ``` **Use object name:** ``` data.objectName ``` **Combine multiple fields:** ``` join('_', [data.groupRef, data.objectName]) ``` **Use operation ID:** ``` data.operationId ``` ## Troubleshooting ### Messages not appearing in Kinesis **Check destination configuration:** 1. Verify that the stream name and region are correct in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). 2. Test your AWS credentials: ```bash theme={null} aws kinesis describe-stream \ --stream-name your-stream-name \ --region your-region ``` 3. Check that IAM permissions for the credential include `kinesis:PutRecord` and `kinesis:PutRecords`. ## Limitations * **Message size**: Individual records cannot exceed 1 MB (AWS Kinesis limit). If a single record is bigger than this limit, we send it to you in a download URL, similar to what we do for webhook destinations. ([More details here](/destinations/webhooks#read-action-webhooks)). * **Throughput**: Limited by Kinesis shard capacity (1 MB/sec write per shard). * **Retention**: Messages are retained for 24 hours to 365 days (configurable in AWS). * **Ordering**: Only guaranteed within a single partition key. If you hit these limits, contact `support@withampersand.com` to discuss scaling strategies. # Overview Source: https://docs.withampersand.com/destinations/overview Ampersand currently supports webhook, Kinesis, and S3 destinations. Destinations allow you to route data synced from SaaS instances via [Read Actions](/read-actions) or [Subscribe Actions](/subscribe-actions), and to receive real-time [Notifications](/notifications) about important lifecycle events in your projects. ## Add a destination to the Ampersand Dashboard Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard to add a new Destination. You'll need to provide: * **Destination name**: this is an alias for the destination that you can then refer to in the `amp.yaml` file. * **URL**: this is the URL of your webhook destination. It must start with `https`. In the case of webhooks, you can easily create a temporary one using tools like [Hookdeck Console](https://console.hookdeck.com?provider=ampersand). ## Using destinations for data delivery For read and subscribe actions, you can specify destinations for each object. You can either have one destination for each object or route multiple objects to the same destination. Here's what your `amp.yaml` file might look like if you had created 2 destinations in the Ampersand Dashboard, one named `accountWebhook` and one named `contactWebhook`. ```YAML YAML theme={null} specVersion: 1.0.0 integrations: - name: readFromSalesforce displayName: Salesforce read provider: salesforce read: objects: - objectName: account destination: accountWebhook - objectName: contact destination: contactWebhook ``` ## Using destinations for notifications Destinations can also be used to receive [notifications](/notifications) about important lifecycle events in your project, such as when customers install or update integrations, when backfills complete, or when sync issues occur. Notifications use a topic-based routing system where you connect event types to topics, and topics to destinations. See the [Notifications overview](/notifications) for complete setup instructions. ## Supported destinations * [Webhook destinations](/destinations/webhooks) * [Amazon Kinesis destinations](/destinations/kinesis) * [Amazon S3 destinations](/destinations/s3) ## Other Destinations We have many other destination types on the roadmap, including: * Postgres * Ampersand-hosted Postgres * Amazon SQS * Google Cloud Storage * Google PubSub * RabbitMQ * Azure Service Bus # Amazon S3 destinations Source: https://docs.withampersand.com/destinations/s3 For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand can write the payload as objects to your Amazon S3 bucket. ## Prerequisites Before setting up an S3 destination, ensure that you have: * An AWS account with access to S3 * An S3 bucket (or permissions to create one) * AWS credentials with the following permission: * `s3:PutObject` ## Create an S3 destination ### Step 1: Set up your S3 bucket If you don't already have an S3 bucket, create one in the AWS Console or using the AWS CLI: ```bash theme={null} aws s3api create-bucket \ --bucket ampersand-integration-bucket \ --region us-west-2 \ --create-bucket-configuration LocationConstraint=us-west-2 ``` ### Step 2: Create AWS credentials Create an IAM user or role with permissions to write to your S3 bucket. You need: * **AWS Access Key ID** * **AWS Secret Access Key** * **AWS Session Token** (optional, for temporary credentials) **Example IAM policy:** ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::ampersand-integration-bucket/*" } ] } ``` ### Step 3: Add the destination to Ampersand Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new S3 destination. You'll need to provide: | Field | Type | Required | Description | | ------------------------- | ------ | -------- | --------------------------------------------------------------- | | **Destination name** | string | Yes | Alias to reference in your `amp.yaml` file | | **Bucket** | string | Yes | Name of your S3 bucket | | **Region** | string | Yes | AWS region where your bucket is located (e.g., `us-west-2`) | | **AWS Access Key ID** | string | Yes | AWS access key with S3 permissions | | **AWS Secret Access Key** | string | Yes | AWS secret access key | | **AWS Session Token** | string | No | Session token for temporary credentials | | **Object key template** | string | No | [JMESPath](https://jmespath.org) template for object key naming | | **Storage class** | string | No | S3 storage class for written objects (defaults to `STANDARD`) | Ampersand encrypts and stores your AWS credentials securely. ## Refer to the destination in your integration After creating your S3 destination, reference it in your `amp.yaml` file: ```yaml theme={null} specVersion: 1.0.0 integrations: - name: salesforceToS3 displayName: Salesforce to S3 provider: salesforce read: objects: - objectName: account destination: ampersandS3Bucket - objectName: contact destination: ampersandS3Bucket ``` ## Message format Ampersand writes each message as a JSON object to your S3 bucket. The object body is the message payload itself — there is no wrapper envelope. In addition to the object body, each S3 object carries event metadata as [S3 object metadata](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html) (`x-amz-meta-*` headers), including `projectId`, `installationId`, `connectionId`, `destinationId`, `operationId`, `objectName`, `event-id`, `timestamp`, and `topic`. ### Read action messages ```json theme={null} { "action": "read", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "hubspot", "groupRef": "group-id-1", "groupName": "group-name-1", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "085353b1-07f1-4209-b471-7e028c0a68c8", "installationUpdateTime": "2025-10-14T02:58:55.595861Z", "objectName": "contacts", "operationId": "0199e0a8-149d-7ee9-9f50-0514403517f9", "operationTime": "2025-10-14T02:58:59.323405308Z", "result": [ { "fields": { "id": "438", "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane" }, "raw": { "archived": false, "createdAt": "2023-10-26T17:55:48.301Z", "id": "438", "properties": { "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane", "createdate": "2023-10-26T17:55:48.301Z" } } } ] } ``` **Key fields:** * `action`: The action type (`read` or `subscribe`) * `projectId`: Your Ampersand project identifier * `provider`: The SaaS provider name (e.g., `hubspot`, `salesforce`) * `groupRef` / `groupName`: Customer group identifier and name * `consumerRef` / `consumerName`: End user identifier and name * `installationId`: The installation instance ID * `installationUpdateTime`: When the installation was last updated * `objectName`: The object being synced * `operationId`: Unique identifier for this sync operation * `operationTime`: When the operation completed * `result`: Array of records synced * `fields`: Normalized field data * `raw`: Original API response from the provider ### Subscribe action messages Subscribe action messages follow the same structure as read actions, but include additional event-related fields within each result entry: ```json theme={null} { "action": "subscribe", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "salesforce", "groupRef": "webhook-demo-group-id", "groupName": "webhook-demo-group-name", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "0c9230e1-8fbe-4b28-bf10-2beee8fbf4ce", "installationUpdateTime": "2025-04-10T23:00:52.618406Z", "objectName": "company", "operationTime": "2025-04-10T23:22:25.000Z", "result": [ { "fields": { "id": "001Dp00000ZDgmxIAD", "annualrevenue": null, "website": null }, "subscribeEventType": "update", "providerEventType": "UPDATE", "raw": { "Id": "001Dp00000ZDgmxIAD", "AnnualRevenue": null, "Description": "Notes about the account", "Name": "Acme Corp", "Website": null, "attributes": { "type": "Account", "url": "/services/data/v59.0/sobjects/Account/001Dp00000ZDgmxIAD" } }, "rawEvent": { "ChangeEventHeader": { "changeOrigin": "com/salesforce/api/soap/63.0;client=SfdcInternalAPI/", "changeType": "UPDATE", "changedFields": ["Description", "LastModifiedDate"], "commitNumber": 12041091891110, "commitTimestamp": 1744327345000, "commitUser": "005Dp000003Cd1SIAS", "entityName": "Account", "recordId": "001Dp00000ZDgmxIAD", "sequenceNumber": 1, "transactionKey": "00051705-2e99-d1e5-7e7a-48e3af682d07" }, "Description": "Notes about the account", "LastModifiedDate": "2025-04-10T23:22:25.000Z" } } ] } ``` **Additional fields in subscribe actions:** * `subscribeEventType`: Normalized event type (`create`, `update`, `delete`, `associationUpdate`) * `providerEventType`: Raw event type from the provider API * `rawEvent`: Original webhook event from the provider (when available) ## Object key configuration Each message is written as a separate object in your bucket. You can customize the object key (the object's path within the bucket) using a JMESPath template. JMESPath is a query language for JSON. See [jmespath.org](https://jmespath.org) for the full specification. You can specify an object key template when creating an S3 destination in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). If you don't specify one, the default key is the message timestamp followed by the message ID: ``` 2025-10-14T02:58:59.323405308Z_0199e0a8-149d-7ee9-9f50-0514403517f9.json ``` ### Template context Your template can reference three namespaces: | Namespace | Fields | | ---------- | ----------------------------------------------------------------------------------------------------------------------------- | | `metadata` | `projectId`, `installationId`, `connectionId`, `destinationId`, `operationId`, `objectName`, `event-id`, `timestamp`, `topic` | | `time` | `year`, `month`, `day`, `hour`, `minute`, `second`, `date`, `datetime`, `unix`, `rfc3339`, `rfc3339_nano` | | `data` | The message payload (e.g., `data.installationId` — see [Message format](#message-format) above) | JMESPath is case-sensitive: `metadata.operationId` works, `metadata.operationid` does not. Fields containing a hyphen must be quoted, e.g. `metadata."event-id"`. Templates are validated when you create or update the destination. ### Custom object key examples **Group objects by synced object name:** ``` join('/', [metadata.objectName, metadata.operationId]) ``` **Partition by date:** ``` join('/', [metadata.objectName, time.date, metadata."event-id"]) ``` **Add a file extension:** ``` join('', [time.rfc3339_nano, '_', metadata."event-id", '.json']) ``` **Use a field from the payload:** ``` join('/', [data.installationId, metadata.operationId]) ``` Make sure your template produces a unique key for every message — include `metadata."event-id"` or `metadata.operationId`. If two messages evaluate to the same key, the later object overwrites the earlier one. ## Storage class By default, objects are written with the `STANDARD` storage class. You can choose a different class when creating the destination, such as `STANDARD_IA`, `ONEZONE_IA`, `INTELLIGENT_TIERING`, `GLACIER`, `GLACIER_IR`, or `DEEP_ARCHIVE`. See [the AWS docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html) for a comparison. ## Troubleshooting ### Objects not appearing in S3 **Check destination configuration:** 1. Verify that the bucket name and region are correct in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). 2. Test your AWS credentials: ```bash theme={null} echo '{}' > test.json aws s3api put-object \ --bucket your-bucket-name \ --key ampersand-test.json \ --body test.json ``` 3. Check that IAM permissions for the credential include `s3:PutObject` on the bucket. ## Limitations * **Message size**: Unlike Kinesis, S3 destinations have no practical message size limit — large payloads are written directly to your bucket. * **One object per message**: Each message becomes a separate S3 object. High-volume syncs produce many small objects, which affects S3 request costs. * **Ordering**: S3 objects are independent; there are no ordering guarantees. Use the `timestamp` object metadata or a time-based key template to order messages. If you have questions about scaling, contact `support@withampersand.com`. # Webhook destinations Source: https://docs.withampersand.com/destinations/webhooks For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand sends a payload to your configured webhook. ## OpenAPI spec Check out our [OpenAPI spec](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml) for the full webhook message schema and more details on the data structure. You can use this to generate types for your language and easily parse the webhook message. ## Read action webhooks For read actions, Ampersand attaches results to a webhook message in two ways: 1. **Inline**: In most cases, the result is directly included in the webhook's `result` field. If a read action returns a large number of records, you might receive multiple webhooks in quick succession, each delivering a subset of the data. Read more about [message frequency](#handling-the-message-frequency) here. 2. **URL**: If a single record in the result set is over 300KB, it becomes too big to be sent over a webhook. In this case, the result won't be included inline. Instead, the webhook message will contain a signed download URL in `resultInfo.downloadUrl`. **The URL expires after 15 minutes.** `resultInfo.type` indicates how the result is attached to the message: If type is `inline`, the full result is in the `result` field. If type is `url`, you can fetch the full result by making a GET request to `resultInfo.downloadUrl`. In both cases, `resultInfo.numRecords` tells you how many records are available in the result. ### Example webhook message (inline) Here's a sample webhook message for a Salesforce Contact object: ```JavaScript JavaScript theme={null} { "action": "read", "consumerRef": "user-123", "consumerName": "John Doe", "groupRef": "xyz-company", "groupName": "XYZ Company", "installationId": "installation-id", "installationUpdateTime": "2024-12-07T00:20:36Z", "objectName": "Contact", "operationId": "operation-uuid", "operationTime": "2024-12-07T00:20:40Z", "projectId": "ampersand-project-id", "provider": "salesforce", // If there is a mapping for this object, rawObjectName contains the original provider API name. "rawObjectName": "Contact", // readType indicates the type of read operation: // - "backfill": Initial backfill. // - "scheduled": Regular scheduled read. // - "triggered": On-demand read triggered via API. "readType": "scheduled", // Only certain providers will have workspace available. "workspace": "salesforce-subdomain", "resultInfo": { "type": "inline", "numRecords": 2 }, "result": [ { "fields": { "id": "001Dp00000P8QurIAF", "firstname": "Sally", "lastname": "Jones" }, "mappedFields": { "pronoun": "she" }, "raw": { "id": "001Dp00000P8QurIAF", "firstname": "Sally", "lastname": "Jones", "pronoun_custom_field": "she" // ... other fields for this record } }, { "fields": { "id": "001Dp00000P9BusEIS", "firstname": "Taylor", "lastname": "Lao" }, "mappedFields": { "pronoun": "they" }, "raw": { "id": "001Dp00000P9BusEIS", "firstname": "Taylor", "lastname": "Lao", "pronoun_custom_field": "they" // ... other fields for this record } } ] } ``` ### Example webhook message (URL) ```JavaScript JavaScript theme={null} { "action": "read", "projectId": "ampersand-project-id", "provider": "salesforce", "groupRef": "xyz-company", "groupName": "XYZ Company", "consumerRef": "user-123", "consumerName": "John Doe", "installationId": "installation-id", "installationUpdateTime": "2024-12-07T00:20:36Z", "objectName": "Contact", "rawObjectName": "Contact", "workspace": "salesforce-subdomain", "operationId": "operation-uuid", "operationTime": "2024-12-07T00:20:40Z", "readType": "backfill", "resultInfo": { "type": "url", "downloadUrl": "https://storage.googleapis.com/....", "numRecords": 1 } } ``` ## Subscribe action webhooks Subscribe action webhooks look very similar to read action webhooks with a few additional fields in each result entry: * **subscribeEventType**: this is the normalized event type string, which can be: `create`, `update`, `delete`, `associationUpdate` (for HubSpot only). * **providerEventType**: this is the raw event type string from the API provider. * **rawEvent**: when available, this is the raw webhook event from the API provider. Here is a sample message: ```JavaScript JavaScript theme={null} { "action": "subscribe", "projectId": "948234676-4874-43a4-beea-fa8081d13a07", "provider": "salesforce", "groupRef": "webhook-demo-group-id", "groupName": "webhook-demo-group-name", "consumerRef": "user-123", "consumerName": "John Doe", "installationId": "0c9230e1c-8fbe-4b28-bf10-2beee8fbf4ce", "installationUpdateTime": "2025-04-10T23:00:52.618406Z", "objectName": "company", "rawObjectName": "account", "workspace": "my-salesforce-workspace", "result": [ { "fields": { "annualrevenue": null, "id": "001Dp00000ZDgmxIAD", "website": null }, "mappedFields": { "accountName": "johnAmp", "notes": "Notes about the account" }, "subscribeEventType": "update", "providerEventType": "UPDATE", "raw": { "AnnualRevenue": null, "Description": "Notes about the account", "Id": "001Dp00000ZDgmxIAD", "Name": "johnAmp", "Website": null, "attributes": { "type": "Account", "url": "/services/data/v59.0/sobjects/Account/001Dp00000ZDgmxIAD" } }, "rawEvent": { "ChangeEventHeader": { "changeOrigin": "com/salesforce/api/soap/63.0;client=SfdcInternalAPI/", "changeType": "UPDATE", "changedFields": [ "Description", "LastModifiedDate" ], "commitNumber": 12041091891110, "commitTimestamp": 1744327345000, "commitUser": "005Dp000003Cd1SIAS", "entityName": "Account", "recordId": "001Dp00000ZDgmxIAD", "sequenceNumber": 1, "transactionKey": "00051705-2e99-d1e5-7e7a-48e3af682d07" }, "Description": "Notes about the account", "LastModifiedDate": "2025-04-10T23:22:25.000Z" } } ], "resultInfo": { "numRecords": 1, "type": "inline" } } ``` ## Handling webhook results Here's some pseudo-code to illustrate how you can get parse result from a webhook: ```go go theme={null} // Generated from the OpenAPI spec type WebhookMessage struct { Action string `json:"action"` GroupName string `json:"groupName"` GroupRef string `json:"groupRef"` ... } func handleWebhookEndpointMessage(message WebhookMessage) { // Assumes that you have already parsed the webhook message into the openapi.WebhookMessage type var results map[string]any switch message.ResultInfo.Type { case "url": // If result type is "url", download the data res, err := http.Get(message.ResultInfo.DownloadUrl) if err != nil { // handle error } results = parseBodyIntoMap(res.Body) case "inline": // If result type is "inline", data is directly in the message results = message.Result default: // This should not happen - contact us if it does! } fmt.Printf("Received %d records", message.ResultInfo.NumRecords) // Process results as needed } ``` ### Handling the payload size The maximum size of a webhook payload Ampersand may send to your destination is **300 KB**. Most API gateways can handle this by default. If you implement your webhook using AWS Lambda or Google Cloud Run Functions, they will also be able to handle this payload size without any additional configuration. If you are using the Node.js framework [Express](https://expressjs.com) in your webhook receiver, and are using the [json middleware](https://expressjs.com/en/api.html#express.json) to parse request bodies, you will need to modify it to increase the maximum request body size. ```js theme={null} const app = express(); app.use([ express.json({ limit: "350kb", // Set the max to 350 KB to allow for some buffer. }), ]); ``` ### Handling the message frequency By default, Ampersand will send you at most 1000 webhook messages per second across all your destinations. If you wish the change this limit, please contact `support@withampersand.com`. Alternatively, you can set the delivery mode to [on request](/read-actions#on-request) to only get webhook messages when you request for them via an API call. ## Webhook signature verification Every webhook message that Ampersand sends includes a cryptographic signature. This signature lets you confirm that the payload is both authentic and unchanged when it reaches your system. Ampersand uses Svix to deliver webhooks to your destination, which is why the signature is sent in the `svix-signature` header. Each destination has its own unique secret, called the "webhook signing secret", which is used to generate the signature. To verify a webhook, you will need to: 1. [Get the signing secret](#viewing-your-secret) for your destination from the Ampersand dashboard. 2. Use that secret with any of the Svix libraries to verify the `svix-signature` header and confirm the message is valid. You can find more information on how to do this [here](https://docs.svix.com/receiving/verifying-payloads/how). #### Viewing your secret To find a destination's webhook signing secret, navigate to the destination in the Ampersand dashboard and click on **"Click here to view the secret"**. 1. Log in to the [Ampersand dashboard](https://dashboard.withampersand.com) & navigate to **[Destinations](https://dashboard.withampersand.com/projects/_/destinations)**. 2. Click on the destination you want to view the secret for. 3. Click on **"Click here to view the secret"**. Alt text #### Rotating your secret If you need to rotate your signing secret for security reasons, you can do so from the Ampersand dashboard. 1. Navigate to the **[Destinations](https://dashboard.withampersand.com/projects/_/destinations)** section in the Ampersand dashboard & click on the destination you want to rotate the secret for. 2. Click on **"Click here to view the secret"**. 3. Click on **"Rotate secret"** to rotate the secret & confirm the rotation. # Dev and prod environments Source: https://docs.withampersand.com/dev-and-prod-environments You can create multiple projects per organization, so we recommend at least creating two projects: * one for your production environment. * one for your development environment, where you can test integrations before shipping them to customers. You can deploy the same set of integrations to all of your projects by defining a single `amp.yaml` file. Then you can use the amp CLI to deploy it to all of your projects: ```bash theme={null} amp login # First deploy to dev amp deploy my-integrations-folder --project=my-dev-project # Promote to prod when you are ready amp deploy my-integrations-folder --project=my-prod-project ``` Please note that for each of your projects, you will need to: * Add Provider Apps to the Ampersand Dashboard. See [Provider Guides](/provider-guides/overview) for more details. Do not reuse the same Provider App across projects, as this will lead to OAuth tokens in one project being invalidated when the same SaaS credential is used to generate tokens in another project. * Generate API keys for embedding UI components or for making API calls. * Add [Destinations](/destinations) to the Ampersand Dashboard if you have any read actions. ## Destination names You should use the same names for the webhook destinations in each of your projects. For example, `my-dev-project` can have a `hubspotWebhook` which has the URL `https://www.my-dev-webhook.com`, and `my-prod-project` can have a `hubspotWebhook` which has the URL `https://www.my-prod-webhook.com`. Then in my `amp.yaml` file, my integration will be defined as follows: ```yaml theme={null} specVersion: 1.0.0 integrations: - name: readHubspotContacts provider: hubspot module: crm read: objects: - objectName: contact # In dev, webhook messages will go to `https://www.my-dev-webhook.com`, # In prod, they will go to `https://www.my-prod-webhook.com`. destination: hubspotWebhook ... ``` ## Deploy to multiple environments with Github Action With the [official Ampersand Github Action](https://github.com/marketplace/actions/official-ampersand-github-action), you can automate promotions between your different environments. Here is a [sample Github workflow](https://github.com/amp-labs/demo-hubspot-apollo/blob/main/.github/workflows/amp_deploy.yml) that deploys to a dev project on commits to a Pull Request, and deploys to a prod project once a Pull Request is merged to the main branch. # Prebuilt UI components Source: https://docs.withampersand.com/embeddable-ui-components ## Project setup ### Prerequisites * You need a React app to embed the components into. If you don't already have one, you can use the [Ampersand starter project](https://github.com/amp-labs/starter-project). * The Ampersand UI library requires **React v18+**. ### Install the Ampersand React library In your repo, use `npm` or `yarn` to install the package: ``` npm install @amp-labs/react ``` or ``` yarn add @amp-labs/react ``` ## AmpersandProvider The `AmpersandProvider` is the foundational component that provides context for all Ampersand functionality, including both headless hooks and prebuilt UI components. It can be placed at any level in your component tree, but it must wrap all Ampersand components and hooks in your application. The `AmpersandProvider` accepts an `options` object with the following required properties: * `project`: Your Ampersand project name or ID (found on your [General Settings page](https://dashboard.withampersand.com/projects/_/settings)) * Authentication method: Either `apiKey` or `getToken` (but not both) ```tsx theme={null} import { AmpersandProvider } from '@amp-labs/react'; import '@amp-labs/react/styles'; const options = { project: 'PROJECT', // Your Ampersand project name or ID // Pick one of the following authentication methods: apiKey: 'API_KEY', // Ampersand API key (simple) // OR getToken: async ({consumerRef, groupRef}) => { // JWT Authentication (advanced) // Custom logic to fetch JWT token from your backend, e.g. return await getTokenFromMyBackend(consumerRef, groupRef); }, }; function App() { return ( {/* All your Ampersand components and hooks go here */} ); } ``` ### Authentication Methods The `AmpersandProvider` supports two authentication methods through the `options` parameter. Only one method can be used at a time. #### API key authentication When you are first developing integrations with Ampersand, API keys are a quick way to get started. You can create an API key on the [API keys page](https://dashboard.withampersand.com/projects/_/api-keys) of your Ampersand Dashboard. Select "UI Library" for "Where will you use the API key?" #### JWT authentication When you are ready to ship your integrations to production, we highly recommend that you move to JWT authentication, as it is more secure than using API keys in the frontend. JWT tokens are time-bound and also enforce that your users only have access to their own integration data. ```tsx theme={null} const options = { project: 'PROJECT', getToken: async ({consumerRef, groupRef}) => { // Custom logic to fetch JWT token from your backend, e.g. return await getTokenFromMyBackend(consumerRef, groupRef); }, }; return ( {/* All your Ampersand components and hooks go here */} ); ``` The `getToken` function should return a promise that resolves to a valid JWT token by making a call to your backend. For detailed information on how to generate JWT tokens, see [JWT Authentication](/api/jwt-auth). ## Components ### Install integration The `InstallIntegration` component prompts your customer for their SaaS credentials, and then guides them through the installation flow for an integration. If they've already installed this integration, then the component will display the current configuration of the integration and allow them to update it. Please note that each [group](/terminology#group) is able to install the integration once, so if someone else with the same `groupRef` has already installed the integration, then the user will not be able to install the same integration again. The parameters of the component are: * **integration** (string): the name of an integration that you've defined in `amp.yaml`. * **consumerRef** (string): the ID that your app uses to identify this end user. * **consumerName** (string, optional): the display name for this end user. * **groupRef** (string): the ID that your app uses to identify a company, team, or workspace. See [group](/terminology#group). * **groupName** (string, optional): the display name for this group. * **onInstallSuccess** (function, optional): a callback function that gets invoked after a consumer successfully installs the integration. * **onUpdateSuccess** (function, optional): a callback function that gets invoked after a consumer successfully updates an existing integration with the new configuration. * **onUninstallSuccess** (function, optional): a callback function that gets invoked after a consumer successfully uninstalls the integration. * **fieldMapping** (JSON): a JSON object that specifies the dynamic mapping configuration for this installation. Learn more about dynamic mapping [here](/object-and-field-mapping#dynamic-field-mappings) Both `onInstallSuccess` and `onUpdateSuccess` should be functions with the following signature: `(installationId: string, config: Config) => void`. `Config` is an exported type from `@amp-labs/react`. ```JSX theme={null} console.log(`Successfully installed ${installationId}` + `with configuration ${JSON.stringify(configObject, null, 2)}`) } onUpdateSuccess = {(installationId, configObject) => console.log(`Successfully updated ${installationId}` + ` with configuration ${JSON.stringify(configObject, null, 2)}`) } onUninstallSuccess = {(installationId) => console.log(`Successfully uninstalled ${installationId}`) } fieldMapping={{ contacts: [ { mapToName: 'priority', mapToDisplayName: 'Priority', prompt: 'Which field do you use to track the priority of a deal?', }, ... // other field mapping options ], }} /> ``` ConnectProvider initial screen InstallIntegration configuration screen #### Wizard mode (Beta) The wizard mode replaces the standard configuration view with a guided, multi-step installation flow during initial set up. It walks your end users through connecting, selecting objects, configuring fields/mappings, and reviewing before creating an installation. The wizard mode is currently in beta preview. The `variant="wizard"` prop is hidden and does not appear in the public TypeScript declarations as of `@amp-labs/react` v2.13.1. See the supported features table below for details on what is currently available. To activate wizard mode, pass `variant="wizard"` to the `InstallIntegration` component: ```JSX theme={null}